bin_struct 0.3.0 → 0.5.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: 44d43c8007a5ad33899e5c7ee6a09c306a64a95012bd489ee2d14d69c7e40dcf
4
- data.tar.gz: db0b44a68cb8a23b1a5110cb12bb587c4cf4a737b42443f72c0ae9241fffa6c9
3
+ metadata.gz: b6e644878c67b11f8006b130b0964a2349f0aa66645f2706cb97a20bf1ec6b77
4
+ data.tar.gz: 0bbf06e91de1bc410888f9c0e79f8caa0b7bb7f7082b5c775186c41c4a3d04db
5
5
  SHA512:
6
- metadata.gz: 287f8dbf64a09b27b5fe376f9b6a982055713cdb2453ab7dd2b5ec0489678727441a3545427bb9f1f31d0ccc733bcc10e6af703fdc3b85b7cf539614b306bc21
7
- data.tar.gz: cc3ba318afa6ac9706d57676256bbebeeec3b4dc52402728777b8c7a7fb89c9de8d893903b3e753d84a25aa6339b9aa919cfd4a223ff1f1a446a2d169047dee2
6
+ metadata.gz: c1bd8b95ce395af08b53ea41385929e9d06490d4ab21588dd46e02d8ba1d66207879174ec2f3a98400667446956042b581a032d93e8b9b97cc2d8448b20ced44
7
+ data.tar.gz: b81c04e4ddcf9a4bbe3af1b36735a5221014f69f43ef5d11c166d33e595aa989f0feb436ed2ba5547f42372c636383a5196c302f54548d0525d49057576f2467
data/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
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.5.0 - 2025-02-17
7
+
8
+ ### Added
9
+
10
+ - Add `String#b` to mimic Ruby's `String`
11
+ - Add a lot of examples in YARD documentation. These examples are checked using yard-doctest.
12
+
13
+ ### Deprecated
14
+
15
+ - Deprecate `BinStruct.force_binary` and `Struct.force_binary` in favor of Ruby's `String#b`
16
+
17
+ ### Fixed
18
+
19
+ - Fix `String#to_s` when static_length is set. `#to_s` was not aware of static length option.
20
+
21
+ ## 0.4.0 - 2025-02-13
22
+
23
+ ### Added
24
+
25
+ - Add `Struct#attribute?` to check existence of an attribute.
26
+ - Add `AbstractTLV.derive` to derive a new subclass from a concrete TLV class.
27
+
28
+ ### Fixed
29
+
30
+ - Update and fix Yard documentation.
31
+
6
32
  ## 0.3.0 - 2024-12-02
7
33
 
8
34
  ### Added
data/README.md CHANGED
@@ -19,6 +19,89 @@ Or add it to a Gemfile:
19
19
  gem 'bin_struct'
20
20
  ```
21
21
 
22
+ ## Usage
23
+
24
+ ### Create a struct
25
+
26
+ To create a BinStruct, create a new class inheriting from `BinStruct::Struct`. Then, defines struct attributes using `.define_attr`. `.define_bit_attr` may also be used to define bit field attributes.
27
+
28
+ ```ruby
29
+ require 'bin_struct'
30
+
31
+ class IPHeader < BinStruct::Struct
32
+ # Define a bir field, defaulting to 0x45, and splitted in 2 sub-fields: version and ihl,
33
+ # 4-bit size each
34
+ define_bit_attr :u8, default: 0x45, version: 4, ihl: 4
35
+ # Define a 8-bit unsigned integer named tos
36
+ # 1st argument: a symbol to define attribute name
37
+ # 2nd argument: a class to define attribute type. May be a type provided by BinStruct,
38
+ # or a user-defined class inheriting from one of these classes
39
+ # others arguments: options. Here, :default defines a default value for the attribute.
40
+ define_attr :tos, BinStruct::Int8, default: 0
41
+ # Define a 16-bit unsigned integer named length. Default to 20.
42
+ define_attr :length, BinStruct::Int16, default: 20
43
+ # Define a 16-bir unsigned integer named id. It is initialized with a random number
44
+ define_attr :id, BinStruct::Int16, default: ->(_) { rand(65_535) }
45
+ # Define a bit field composed of 4 subfields of 1, 1, 1 and 13 bit, respectively
46
+ define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13
47
+ # Define TTL field, a 8-bit unsigned integer, default to 64
48
+ define_attr :ttl, BinStruct::Int8, default: 64
49
+ # Define protocol field (8-bit unsigned integer)
50
+ define_attr :protocol, BinStruct::Int8
51
+ # Define checksum field (16-bit unsigned integer), default to 0
52
+ define_attr :checksum, BinStruct::Int16, default: 0
53
+ # Source and destination addresses, defined as array of 4 8-bit unsigned integers
54
+ define_attr :src, BinStruct::ArrayOfInt8, length_from: -> { 4 }
55
+ define_attr :dst, BinStruct::ArrayOfInt8, length_from: -> { 4 }
56
+ end
57
+ ```
58
+
59
+ ### Parse a binary string
60
+
61
+ ```ruby
62
+ # Initialize struct from a binary string
63
+ ip = IPHeader.new.read("\x45\x00\x00\x14\x43\x21\x00\x00\x40\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01".b)
64
+
65
+ # Access some fields
66
+ p ip.version #=> 4
67
+ p ip.ihl #=> 5
68
+ p ip.id.to_s(16) #=> "4321"
69
+ p ip.protocol #=> 1
70
+ p ip.src.map { |byte| byte.to_i }.join('.') #=> "127.0.0.1"
71
+ ```
72
+
73
+ ```text
74
+ > p IPHeader.new.read("\x45\x00\x00\x14\x43\x21\x00\x00\x40\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01")
75
+ -- IPHeader -----------------------------------------------------------
76
+ BitAttr8 u8: 69 (0x45)
77
+ version:4 ihl:5
78
+ Int8 tos: 0 (0x00)
79
+ Int16 length: 20 (0x0014)
80
+ Int16 id: 17185 (0x4321)
81
+ BitAttr16 frag: 0 (0x0000)
82
+ flag_rsv:0 flag_df:0 flag_mf:0 fragment_offset:0
83
+ Int8 ttl: 64 (0x40)
84
+ Int8 protocol: 1 (0x01)
85
+ Int16 checksum: 0 (0x0000)
86
+ ArrayOfInt8 src: 127,0,0,1
87
+ ArrayOfInt8 dst: 127,0,0,1
88
+
89
+ ```
90
+
91
+ ### Generate a binary string
92
+
93
+ ```ruby
94
+ # Create a new struct with some fields initialized
95
+ ip = IPHeader.new(tos: 42, id: 0x1234)
96
+
97
+ # Initialize fields after creation
98
+ ip.src = [192, 168, 1, 1]
99
+ ip.dst = [192, 168, 1, 2]
100
+
101
+ # Generate binary string
102
+ ip.to_s
103
+ ```
104
+
22
105
  ## License
23
106
 
24
107
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -6,14 +6,11 @@
6
6
  # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
7
  # This program is published under MIT license.
8
8
 
9
- # BinStruct module
10
- # @author LemonTree55
11
9
  module BinStruct
12
10
  # @abstract Base class to define type-length-value data.
13
11
  #
14
- # ===Usage
15
- # To simply define a new TLV class, do:
16
- # MyTLV = PacketGen::Types::AbstractTLV.create
12
+ # You have to define a concrete class from AbstractTLV
13
+ # MyTLV = BinStruct::AbstractTLV.create
17
14
  # MyTLV.define_type_enum 'one' => 1, 'two' => 2
18
15
  # This will define a new +MyTLV+ class, subclass of {AbstractTLV}. This class will
19
16
  # define 3 attributes:
@@ -23,29 +20,36 @@ module BinStruct
23
20
  # +.define_type_enum+ is, here, necessary to define enum hash to be used
24
21
  # for +#type+ accessor, as this one is defined as an {Enum}.
25
22
  #
26
- # This new defined class may now be easily used:
23
+ # @example Basic usage
24
+ # MyTLV = BinStruct::AbstractTLV.create
25
+ # MyTLV.define_type_enum 'one' => 1, 'two' => 2
26
+ #
27
27
  # tlv = MyTLV.new(type: 1, value: 'abcd') # automagically set #length from value
28
28
  # tlv.type #=> 1
29
29
  # tlv.human_type #=> 'one'
30
30
  # tlv.length #=> 4
31
31
  # tlv.value #=> "abcd"
32
32
  #
33
- # ===Advanced usage
34
- # Each attribute's type may be changed at generating TLV class:
35
- # MyTLV = PacketGen::Types::AbstractTLV.create(type_class: PacketGen::Types::Int16,
36
- # length_class: PacketGen::Types::Int16,
37
- # value_class: PacketGen::Header::IP::Addr)
38
- # tlv = MyTLV.new(type: 1, value: '1.2.3.4')
33
+ # @example Change attribute types
34
+ # # Change type for each attribute
35
+ # # Type and length are 16-bit big endian integers
36
+ # # Value is a OUI
37
+ # MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
38
+ # length_class: BinStruct::Int16,
39
+ # value_class: BinStruct::OUI)
40
+ # tlv = MyTLV.new(type: 1, value: '01:02:03')
39
41
  # tlv.type #=> 1
40
- # tlv.length #=> 4
41
- # tlv.value #=> '1.2.3.4'
42
- # tlv.to_s #=> "\x00\x01\x00\x04\x01\x02\x03\x04"
42
+ # tlv.length #=> 3
43
+ # tlv.value #=> '01:02:03'
44
+ # tlv.to_s #=> "\x00\x01\x00\x03\x01\x02\x03"
43
45
  #
44
- # Some aliases may also be defined. For example, to create a TLV type
45
- # whose +type+ attribute should be named +code+:
46
- # MyTLV = PacketGen::Types::AbstractTLV.create(type_class: PacketGen::Types::Int16,
47
- # length_class: PacketGen::Types::Int16,
48
- # aliases: { code: :type })
46
+ # @example Using aliases
47
+ # # Type and length are 16-bit big endian integers
48
+ # # Value is a string
49
+ # # code is an alias for type
50
+ # MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
51
+ # length_class: BinStruct::Int16,
52
+ # aliases: { code: :type })
49
53
  # tlv = MyTLV.new(code: 1, value: 'abcd')
50
54
  # tlv.code #=> 1
51
55
  # tlv.type #=> 1
@@ -77,11 +81,12 @@ module BinStruct
77
81
  # in the desired order.
78
82
  # @param [::String] attr_in_length give attributes to compute length on.
79
83
  # @return [Class]
84
+ # @raise [Error] Called on {AbstractTLV} subclass
80
85
  def create(type_class: Int8Enum, length_class: Int8, value_class: String,
81
86
  aliases: {}, attr_order: 'TLV', attr_in_length: 'V')
82
87
  unless equal?(AbstractTLV)
83
88
  raise Error,
84
- '.create cannot be called on a subclass of PacketGen::Types::AbstractTLV'
89
+ '.create cannot be called on a subclass of BinStruct::AbstractTLV'
85
90
  end
86
91
 
87
92
  klass = Class.new(self)
@@ -91,7 +96,7 @@ module BinStruct
91
96
  check_attr_in_length(attr_in_length)
92
97
  check_attr_order(attr_order)
93
98
  generate_attributes(klass, attr_order, type_class, length_class, value_class)
94
-
99
+ generate_aliases_for(klass, aliases)
95
100
  aliases.each do |al, orig|
96
101
  klass.instance_eval do
97
102
  alias_method al, orig if klass.method_defined?(orig)
@@ -103,6 +108,50 @@ module BinStruct
103
108
  end
104
109
  # rubocop:enable Metrics/ParameterLists
105
110
 
111
+ # On inheritage, copy aliases and attr_in_length
112
+ # @param [Class] klass inheriting class
113
+ # @return [void]
114
+ # @since 0.4.0
115
+ # @author LemonTree55
116
+ def inherited(klass)
117
+ super
118
+
119
+ aliases = @aliases.clone
120
+ attr_in_length = @attr_in_length.clone
121
+
122
+ klass.class_eval do
123
+ @aliases = aliases
124
+ @attr_in_length = attr_in_length
125
+ end
126
+ end
127
+
128
+ # Derive a new TLV class from an existing one
129
+ # @param [Class,nil] type_class New class to use for +type+. Unchanged if +nil+.
130
+ # @param [Class,nil] length_class New class to use for +length+. Unchanged if +nil+.
131
+ # @param [Class,nil] value_class New class to use for +value+. Unchanged if +nil+.
132
+ # @return [Class]
133
+ # @raise [Error] Called on {AbstractTLV} class
134
+ # @since 0.4.0
135
+ # @author LemonTree55
136
+ # @example
137
+ # # TLV with type and length on 16 bits, value is a BinStruct::String
138
+ # FirstTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16, length_class: BinStruct::Int16)
139
+ # # TLV with same type and length classes than FirstTLV, but value is an array of Int8
140
+ # SecondTLV = FirstTLV.derive(value_class: BinStruct::ArrayOfInt8)
141
+ def derive(type_class: nil, length_class: nil, value_class: nil, aliases: {})
142
+ raise Error, ".derive cannot be called on #{name}" if equal?(AbstractTLV)
143
+
144
+ klass = Class.new(self)
145
+ klass.aliases.merge!(aliases)
146
+ generate_aliases_for(klass, aliases)
147
+
148
+ klass.attr_defs[:type].type = type_class unless type_class.nil?
149
+ klass.attr_defs[:length].type = length_class unless length_class.nil?
150
+ klass.attr_defs[:value].type = value_class unless value_class.nil?
151
+
152
+ klass
153
+ end
154
+
106
155
  # @!attribute type
107
156
  # @abstract
108
157
  # Type attribute for real TLV class
@@ -168,6 +217,15 @@ module BinStruct
168
217
  end
169
218
  end
170
219
  end
220
+
221
+ def generate_aliases_for(klass, aliases)
222
+ aliases.each do |al, orig|
223
+ klass.instance_eval do
224
+ alias_method al, orig if klass.method_defined?(orig)
225
+ alias_method :"#{al}=", :"#{orig}=" if klass.method_defined?(:"#{orig}=")
226
+ end
227
+ end
228
+ end
171
229
  end
172
230
 
173
231
  # @!attribute type
@@ -9,7 +9,7 @@
9
9
  require 'forwardable'
10
10
 
11
11
  module BinStruct
12
- # @abstract Base class to define set of {Struct} subclasses.
12
+ # @abstract Base class to define set of {Structable} subclasses.
13
13
  #
14
14
  # This class mimics regular Ruby Array, but it is {Structable} and responds to {LengthFrom}.
15
15
  #
@@ -44,10 +44,11 @@ module BinStruct
44
44
  # @!method clear
45
45
  # Clear array.
46
46
  # @return [void]
47
+ # @see #clear!
47
48
  # @!method each
48
49
  # Calls the given block once for each element in self, passing that
49
- # element as a parameter. Returns the array itself.
50
- # @return [::Array]
50
+ # element as a parameter. Returns the array itself, or an enumerator if no block is given.
51
+ # @return [::Array, Enumerator]
51
52
  # @method empty?
52
53
  # Return +true+ if contains no element.
53
54
  # @return [Boolean]
@@ -86,6 +87,23 @@ module BinStruct
86
87
 
87
88
  # @param [Hash] options
88
89
  # @option options [Int] counter Int object used as a counter for this set
90
+ # @example counter example
91
+ # # Define a counter
92
+ # counter = BinStruct::Int8.new
93
+ # counter.to_i # => 0
94
+ #
95
+ # # Define an array with associated counter
96
+ # ary = BinStruct::ArrayOfInt8.new(counter: counter)
97
+ # # Add 2 elements to arry, increment counter twice
98
+ # ary.read([1, 2])
99
+ # counter.to_i #=> 2
100
+ # # Add a third element
101
+ # ary << BinStruct::Int8.new(value: 42)
102
+ # counter.to_i #=> 3
103
+ #
104
+ # # push does not increment the counter
105
+ # ary.push(BinStruct::Int8.new(value: 100))
106
+ # counter.to_i #=> 3
89
107
  def initialize(options = {})
90
108
  @counter = options[:counter]
91
109
  @array = []
@@ -112,6 +130,7 @@ module BinStruct
112
130
 
113
131
  # Clear array. Reset associated counter, if any.
114
132
  # @return [void]
133
+ # @see #clear
115
134
  def clear!
116
135
  @array.clear
117
136
  @counter&.from_human(0)
@@ -137,7 +156,8 @@ module BinStruct
137
156
 
138
157
  # @abstract depend on private method +#record_from_hash+ which should be
139
158
  # declared by subclasses.
140
- # Add an object to this array. Do not update associated counter.
159
+ # Add an object to this array. Do not update associated counter. If associated must be incremented, use
160
+ # {#<<}
141
161
  # @param [Object] obj type depends on subclass
142
162
  # @return [self]
143
163
  # @see #<<
@@ -154,9 +174,11 @@ module BinStruct
154
174
 
155
175
  # @abstract depend on private method +#record_from_hash+ which should be
156
176
  # declared by subclasses.
157
- # Add an object to this array, and increment associated counter, if any
177
+ # Add an object to this array, and increment associated counter, if any. If associated counter must not be
178
+ # incremented, use {#push}.
158
179
  # @param [Object] obj type depends on subclass
159
180
  # @return [self]
181
+ # @see #push
160
182
  def <<(obj)
161
183
  push(obj)
162
184
  @counter&.from_human(@counter.to_i + 1)
@@ -266,30 +288,65 @@ module BinStruct
266
288
  end
267
289
 
268
290
  # Specialized {Array} to handle serie of {Int8}.
291
+ # @example
292
+ # ary = BinStruct::ArrayOfInt8.new
293
+ # ary.read([0, 1, 2])
294
+ # ary.to_s #=> "\x00\x01\x02".b
295
+ #
296
+ # ary.read("\x05\x06")
297
+ # ary.map(&:to_i) #=> [5, 6]
269
298
  class ArrayOfInt8 < Array
270
299
  include ArrayOfIntMixin
271
300
  set_of Int8
272
301
  end
273
302
 
274
303
  # Specialized {Array} to handle serie of {Int16}.
304
+ # @example
305
+ # ary = BinStruct::ArrayOfInt16.new
306
+ # ary.read([0, 1, 2])
307
+ # ary.to_s #=> "\x00\x00\x00\x01\x00\x02".b
308
+ #
309
+ # ary.read("\x05\x06")
310
+ # ary.map(&:to_i) #=> [0x0506]
275
311
  class ArrayOfInt16 < Array
276
312
  include ArrayOfIntMixin
277
313
  set_of Int16
278
314
  end
279
315
 
280
316
  # Specialized {Array} to handle serie of {Int16le}.
317
+ # @example
318
+ # ary = BinStruct::ArrayOfInt16le.new
319
+ # ary.read([0, 1, 2])
320
+ # ary.to_s #=> "\x00\x00\x01\x00\x02\x00".b
321
+ #
322
+ # ary.read("\x05\x06")
323
+ # ary.map(&:to_i) #=> [0x0605]
281
324
  class ArrayOfInt16le < Array
282
325
  include ArrayOfIntMixin
283
326
  set_of Int16le
284
327
  end
285
328
 
286
329
  # Specialized {Array} to handle serie of {Int32}.
330
+ # @example
331
+ # ary = BinStruct::ArrayOfInt32.new
332
+ # ary.read([0, 1, 2])
333
+ # ary.to_s #=> "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".b
334
+ #
335
+ # ary.read("\x00\x00\x05\x06")
336
+ # ary.map(&:to_i) #=> [0x00000506]
287
337
  class ArrayOfInt32 < BinStruct::Array
288
338
  include ArrayOfIntMixin
289
339
  set_of Int32
290
340
  end
291
341
 
292
342
  # Specialized {Array} to handle serie of {Int32le}.
343
+ # @example
344
+ # ary = BinStruct::ArrayOfInt32le.new
345
+ # ary.read([0, 1, 2])
346
+ # ary.to_s #=> "\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00".b
347
+ #
348
+ # ary.read("\x00\x00\x05\x06")
349
+ # ary.map(&:to_i) #=> [0x06050000]
293
350
  class ArrayOfInt32le < BinStruct::Array
294
351
  include ArrayOfIntMixin
295
352
  set_of Int32le
@@ -7,21 +7,32 @@
7
7
  require 'digest'
8
8
 
9
9
  module BinStruct
10
- # Define a bitfield attribute to embed in a {Struct}.
10
+ # Define a bitfield attribute to embed in a {Struct}. Use it through {Struct.define_bit_attr}
11
11
  #
12
+ # @example
12
13
  # class MyStruct < BinStruct::Struct
13
14
  # # Create a 32-bit bitfield attribute, with fields a (16 bits), b and c (4 bits each) and d (8 bits).
14
15
  # # 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
+ # define_bit_attr :int32, width: 32, a: 16, b: 4, c: 4, d:8
16
17
  # end
18
+ #
19
+ # s1 = MyStruct.new(int32: 0x12345678)
20
+ # s1.a #=> 0x1234
21
+ # s1.b #=> 5
22
+ # s1.c #=> 6
23
+ # s1.d #=> 0x78
24
+ #
25
+ # s2 = MyStruct.new(a: 0x1234, d: 0x42)
26
+ # s2.to_s #=> "\x12\x34\x00\x42".b
17
27
  # @since 0.3.0
28
+ # @abstract Subclasses must de derived using {.create}.
18
29
  # @author LemonTree55
19
30
  class BitAttr
20
31
  include Structable
21
32
 
22
33
  # @return [Integer] width in bits of bit attribute
23
34
  attr_reader :width
24
- # @return [Array[Symbol]]
35
+ # @return [::Array[Symbol]]
25
36
  attr_reader :bit_methods
26
37
 
27
38
  # @private
@@ -79,9 +90,10 @@ module BinStruct
79
90
  # @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
80
91
  # initialization values
81
92
  # @return [self]
93
+ # @raise [NotImplementedError] raised when called on {BitAttr} class
82
94
  def initialize(opts = {})
83
95
  parameters = self.class.parameters
84
- raise NotImplementedError, '#initialize may only be called on subclass of {self.class}' if parameters.nil?
96
+ raise NotImplementedError, "#initialize may only be called on subclass of #{self.class}" if parameters.nil?
85
97
 
86
98
  @width = parameters.width
87
99
  @fields = parameters.fields
@@ -110,7 +122,7 @@ module BinStruct
110
122
  end
111
123
 
112
124
  # Populate bit attribute from +str+
113
- # @param [::String,nil] str
125
+ # @param [#to_s,nil] str
114
126
  # @return [self]
115
127
  def read(str)
116
128
  return self if str.nil?
@@ -175,7 +187,7 @@ module BinStruct
175
187
  if size == 1
176
188
  instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
177
189
  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
190
+ "@data[#{name.inspect}] = v; end", __FILE__, __LINE__ - 1
179
191
  bit_methods << :"#{name}?"
180
192
  else
181
193
  instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
@@ -10,6 +10,9 @@ require 'forwardable'
10
10
 
11
11
  module BinStruct
12
12
  # This class handles null-terminated strings (aka C strings).
13
+ # # @example
14
+ # cstr = BinStruct::CString.new(value: 'abcd')
15
+ # cstr.to_s #=> "abcd\x00".b
13
16
  # @author Sylvain Daubert (2016-2024)
14
17
  # @author LemonTree55
15
18
  class CString
@@ -76,14 +79,14 @@ module BinStruct
76
79
 
77
80
  # @param [Hash] options
78
81
  # @option options [Integer] :static_length set a static length for this string
79
- # @option options [::String] :value string value (default to +''+)
82
+ # @option options [::String] :value string value (default to +""+)
80
83
  def initialize(options = {})
81
84
  register_internal_string(options[:value] || +'')
82
85
  @static_length = options[:static_length]
83
86
  end
84
87
 
85
88
  # Populate self from binary string
86
- # @param [::String] str
89
+ # @param [#to_s] str
87
90
  # @return [self]
88
91
  def read(str)
89
92
  s = str.to_s
@@ -102,7 +105,7 @@ module BinStruct
102
105
  else
103
106
  s = "#{string}\x00"
104
107
  end
105
- BinStruct.force_binary(s)
108
+ s.b
106
109
  end
107
110
 
108
111
  # Append the given string to CString
@@ -134,7 +137,7 @@ module BinStruct
134
137
  # @param [::String] str
135
138
  # @return [self]
136
139
  def from_human(str)
137
- read str
140
+ read(str)
138
141
  end
139
142
 
140
143
  # Get human-readable string
@@ -146,8 +149,7 @@ module BinStruct
146
149
  private
147
150
 
148
151
  def register_internal_string(str)
149
- @string = str
150
- BinStruct.force_binary(@string)
152
+ @string = str.b
151
153
  end
152
154
 
153
155
  def remove_null_character
@@ -12,20 +12,23 @@ module BinStruct
12
12
  # An {Enum} type is used to handle an {Int} attribute with limited
13
13
  # and named values.
14
14
  #
15
- # == Simple example
16
- # enum = Int8Enum.new('low' => 0, 'medium' => 1, 'high' => 2})
17
- # In this example, +enum+ is a 8-bit attribute which may take one
18
- # among three values: +low+, +medium+ or +high+:
19
- # enum.value = 'high'
20
- # enum.value # => 2
21
- # enum.value = 1
22
- # enum.value # => 1
23
- # enum.to_human # => "medium"
24
- # Setting an unknown value will raise an exception:
25
- # enum.value = 4 # => raise!
26
- # enum.value = 'unknown' # => raise!
27
- # But {#read} will not raise when reading an outbound value. This
15
+ # Setting an unknown name will raise an exception:
16
+ # enum.value = 'unknown' # => raise!
17
+ # But {#read} and {#value=} will not raise when reading/setting an out-of-bound integer. This
28
18
  # to enable decoding (or forging) of bad packets.
19
+ # enum.read("\x05".b).value # => 5
20
+ # enum.value = 4 # => 4
21
+ #
22
+ # @example Simple example
23
+ # # Define an enum on 8-bit integer. It may take one among
24
+ # # three values: low, medium or high
25
+ # enum = BinStruct::Int8Enum.new(enum: {'low' => 0, 'medium' => 1, 'high' => 2})
26
+ # enum.value = 'high'
27
+ # enum.value # => 2
28
+ # enum.value = 1
29
+ # enum.value # => 1
30
+ # enum.to_human # => "medium"
31
+ #
29
32
  # @author Sylvain Daubert (2016-2024)
30
33
  # @author LemonTree55
31
34
  class Enum < Int
@@ -55,7 +55,7 @@ module BinStruct
55
55
 
56
56
  # @abstract
57
57
  # @return [::String]
58
- # @raise [Error] This is an abstrat method and must be redefined
58
+ # @raise [Error] This is an abstract method and must be redefined
59
59
  def to_s
60
60
  raise Error, 'BinStruct::Int#to_s is abstract' unless defined? @packstr
61
61
 
@@ -9,6 +9,13 @@
9
9
  module BinStruct
10
10
  # Provides a class for creating strings preceeded by their length as an {Int}.
11
11
  # By default, a null string will have one byte length (length byte set to 0).
12
+ # == Examples
13
+ # # IntString with 8-bit length
14
+ # is8 = BinStruct::IntString.new(value: "abcd")
15
+ # is8.to_s # => "\x04abcd"
16
+ # # IntString with 16-bit length
17
+ # is16 = BinStruct::IntString.new(length_type: BinStruct::Int16le, value: "abcd")
18
+ # is16.to_s # => "\x04\x00abcd"
12
19
  # @author Sylvain Daubert (2016-2024)
13
20
  # @author LemonTree55
14
21
  class IntString
@@ -9,7 +9,7 @@
9
9
  module BinStruct
10
10
  # This module is a mixin adding +length_from+ capacity to a type.
11
11
  # +length_from+ capacity is the capacity, for a type, to gets its
12
- # length from another object.
12
+ # length from another object. For an example, see {String}.
13
13
  # @author Sylvain Daubert (2016-2024)
14
14
  # @author LemonTree55
15
15
  module LengthFrom
@@ -30,7 +30,7 @@ module BinStruct
30
30
  # @param [#to_s] str
31
31
  # @return [::String]
32
32
  def read_with_length_from(str)
33
- s = BinStruct.force_binary(str.to_s)
33
+ s = str.to_s.b
34
34
  s[0, sz_to_read]
35
35
  end
36
36
 
@@ -8,10 +8,11 @@
8
8
 
9
9
  module BinStruct
10
10
  # OUI type, defined as a set of 3 bytes
11
- # oui = OUI.new
11
+ # @example
12
+ # oui = BinStruct::OUI.new
12
13
  # oui.from_human('00:01:02')
13
14
  # oui.to_human # => "00:01:02"
14
- # oui.to_s # => "\x00\x01\x03"
15
+ # oui.to_s # => "\x00\x01\x02".b
15
16
  # @author Sylvain Daubert (2016-2024)
16
17
  # @author LemonTree55
17
18
  class OUI < Struct
@@ -10,6 +10,33 @@ require 'forwardable'
10
10
 
11
11
  module BinStruct
12
12
  # This class mimics regular String, but it is {Structable}.
13
+ #
14
+ # It may take its length from another field ({LengthFrom} capacity). It may also has a static length
15
+ # (i.e. string has always the same length, whatever its content is).
16
+ #
17
+ # @example Basic example
18
+ # str = BinStruct::String.new
19
+ # str.read("abc")
20
+ # str.to_s #=> "abc".b
21
+ #
22
+ # @example LengthFrom example
23
+ # class StrLen < BinStruct::Struct
24
+ # define_attr :length, BinStruct::Int8
25
+ # define_attr :str, BinStruct::String, builder: ->(h, t) { t.new(length_from: h[:length]) }
26
+ # end
27
+ #
28
+ # # Length is 3, but rest of data is 4 byte long. Only 3 bytes will be read.
29
+ # s = StrLen.new.read("\x03abcd")
30
+ # s.length #=> 3
31
+ # s.str.to_s #=> "abc".b
32
+ # s.to_s # => "\x03abc".b
33
+ #
34
+ # @example static length example
35
+ # s = BinStruct::String.new(static_length: 10)
36
+ # s.sz #=> 10
37
+ # s.to_s #=> "\0\0\0\0\0\0\0\0\0\0".b
38
+ # s.read("01234567890123456789")
39
+ # s.to_s #=> "0123456789".b
13
40
  # @author Sylvain Daubert (2016-2024)
14
41
  # @author LemonTree55
15
42
  class String
@@ -19,7 +46,7 @@ module BinStruct
19
46
 
20
47
  def_delegators :@string, :[], :length, :size, :inspect, :==,
21
48
  :unpack, :force_encoding, :encoding, :index, :empty?,
22
- :encode, :slice, :slice!, :[]=
49
+ :encode, :slice, :slice!, :[]=, :b
23
50
 
24
51
  # Underlying Ruby String
25
52
  # @return [::String]
@@ -32,7 +59,7 @@ module BinStruct
32
59
  # @option options [Int,Proc] :length_from object or proc from which
33
60
  # takes length when reading
34
61
  # @option options [Integer] :static_length set a static length for this string
35
- # @option options [::String] :value string value (default to +''+)
62
+ # @option options [::String] :value string value (default to +""+)
36
63
  def initialize(options = {})
37
64
  register_internal_string(options[:value] || +'')
38
65
  initialize_length_from(options)
@@ -47,7 +74,7 @@ module BinStruct
47
74
  end
48
75
 
49
76
  # Populate String from a binary String. Limit length using {LengthFrom} or {#static_length}, if one is set.
50
- # @param [::String] str
77
+ # @param [::String,nil] str
51
78
  # @return [self]
52
79
  def read(str)
53
80
  s = read_with_length_from(str)
@@ -83,25 +110,29 @@ module BinStruct
83
110
  # @param [#to_s] str
84
111
  # @return [self]
85
112
  def <<(str)
86
- @string << BinStruct.force_binary(str.to_s)
113
+ @string << str.to_s.b
87
114
  self
88
115
  end
89
116
 
90
- # Generate binary string
117
+ # Generate "binary" string
91
118
  # @return [::String]
92
119
  def to_s
93
- @string
120
+ if static_length?
121
+ s = @string[0, static_length]
122
+ s << ("\x00" * (static_length - s.length))
123
+ s.b
124
+ else
125
+ @string.b
126
+ end
94
127
  end
95
128
 
96
- alias sz length
97
129
  alias to_human to_s
98
130
  alias from_human read
99
131
 
100
132
  private
101
133
 
102
134
  def register_internal_string(str)
103
- @string = str
104
- BinStruct.force_binary(@string)
135
+ @string = str.b
105
136
  end
106
137
  end
107
138
  end
@@ -32,8 +32,8 @@ module BinStruct
32
32
  # Attributes may also be accessed through {#[]} ans {#[]=}. These methods give access
33
33
  # to type object:
34
34
  # mybs = MyBinaryStructure.new
35
- # mybs.attr1 # => Integer
36
- # mybs[:attr1] # => BinStruct::Int8
35
+ # mybs.attr1.class # => Integer
36
+ # mybs[:attr1].class # => BinStruct::Int8
37
37
  #
38
38
  # {#initialize} accepts an option hash to populate attributes. Keys are attribute
39
39
  # name symbols, and values are those expected by writer accessor.
@@ -57,7 +57,7 @@ module BinStruct
57
57
  # +#body+, +#body=+, +#mac_addr+ and +#mac_addr=+.
58
58
  #
59
59
  # {.define_attr} has many options (third optional Hash argument):
60
- # * +:default+ gives default attribute value. It may be a simple value (an Integer
60
+ # * +:default+ to define default attribute value. It may be a simple value (an Integer
61
61
  # for an Int attribute, for example) or a lambda,
62
62
  # * +:builder+ to give a builder/constructor lambda to create attribute. The lambda
63
63
  # takes 2 arguments: {Struct} subclass object owning attribute, and type class as passes
@@ -97,7 +97,7 @@ module BinStruct
97
97
  # * {.define_attr_before} and {.define_bit_attr_before} to define a new attribute before an existing one,
98
98
  # * {.define_attr_after} and {.define_bit_attr_after} to define a new attribute after an existing onr,
99
99
  # * {.remove_attr} to remove an existing attribute,
100
- # * {.uptade_attr} to change options of an attribute (but not its type),
100
+ # * {.update_attr} to change options of an attribute (but not its type),
101
101
  #
102
102
  # @author Sylvain Daubert (2016-2024)
103
103
  # @author LemonTree55
@@ -247,7 +247,7 @@ module BinStruct
247
247
  # class MyHeader < BinStruct::Struct
248
248
  # # define a 16-bit attribute named :flag
249
249
  # # flag1, flag2 and flag3 are 1-bit attributes
250
- # # type and stype are 3-bit attributes. reserved is a 7-bit attribute
250
+ # # type and stype are 3-bit attributes, reserved is a 7-bit attribute
251
251
  # define_bit_attr :flags, flag1: 1, flag2: 1, flag3: 1, type: 3, stype: 3, reserved: 7
252
252
  # end
253
253
  # A bit attribute of size 1 bit defines 3 methods:
@@ -401,6 +401,15 @@ module BinStruct
401
401
  @attributes[attr] = obj
402
402
  end
403
403
 
404
+ # Say if struct has given attribute
405
+ # @param [Symbol] attr attribute name
406
+ # @return [Boolean]
407
+ # @since 0.4.0
408
+ # @author LemonTree55
409
+ def attribute?(attr)
410
+ @attributes.key?(attr)
411
+ end
412
+
404
413
  # Get all attribute names
405
414
  # @return [Array<Symbol>]
406
415
  def attributes
@@ -434,12 +443,11 @@ module BinStruct
434
443
  def read(str)
435
444
  return self if str.nil?
436
445
 
437
- force_binary(str)
438
446
  start = 0
439
447
  attributes.each do |attr|
440
448
  next unless present?(attr)
441
449
 
442
- obj = self[attr].read(str[start..])
450
+ obj = self[attr].read(str.b[start..])
443
451
  start += self[attr].sz
444
452
  self[attr] = obj unless obj == self[attr]
445
453
  end
@@ -471,7 +479,7 @@ module BinStruct
471
479
  # @return [String]
472
480
  def to_s
473
481
  attributes.select { |attr| present?(attr) }
474
- .map! { |attr| force_binary @attributes[attr].to_s }.join
482
+ .map! { |attr| @attributes[attr].to_s.b }.join
475
483
  end
476
484
 
477
485
  # Size of object as binary string
@@ -522,6 +530,7 @@ module BinStruct
522
530
  # Force str to binary encoding
523
531
  # @param [String] str
524
532
  # @return [String]
533
+ # @deprecated Prefer use of Ruby's {::String#b}
525
534
  def force_binary(str)
526
535
  BinStruct.force_binary(str)
527
536
  end
@@ -575,7 +584,7 @@ module BinStruct
575
584
  # @return [String]
576
585
  def inspect_titleize
577
586
  title = self.class.to_s
578
- +"-- #{title} #{'-' * (66 - title.length)}\n"
587
+ "-- #{title} #{'-' * (66 - title.length)}\n"
579
588
  end
580
589
 
581
590
  # @param [:Symbol] attr
@@ -8,5 +8,5 @@
8
8
 
9
9
  module BinStruct
10
10
  # BinStruct version
11
- VERSION = '0.3.0'
11
+ VERSION = '0.5.0'
12
12
  end
data/lib/bin_struct.rb CHANGED
@@ -8,7 +8,31 @@
8
8
 
9
9
  require_relative 'bin_struct/version'
10
10
 
11
- # BinStruct module
11
+ # BinStruct module provides classes to easily serialize/deserialize data to/from binary strings.
12
+ # @example Basic example
13
+ # class MyData < BinStruct::Struct
14
+ # # Define 2 attributes as a 8-bit integer
15
+ # define_attr :byte1, BinStruct::Int8
16
+ # define_attr :byte2, BinStruct::Int8
17
+ # # Define an attribute as a 16-bit big endian integer
18
+ # define_attr :word, BinStruct::Int16
19
+ # # Define a 32-bit little endian integer attribute
20
+ # define_attr :dword, BinStruct::Int32le
21
+ # # Define a string prepending with its length (8-bit integer)
22
+ # define_attr :str, BinStruct::IntString
23
+ # end
24
+ #
25
+ # # Generate binary data
26
+ # mydata = MyData.new(byte1: 1, byte2: 2, word: 3, dword: 4, str: 'abc')
27
+ # mydata.to_s #=> "\x01\x02\x00\x03\x04\x00\x00\x00\x03abc".b
28
+ #
29
+ # # Parse binary data
30
+ # mydata.read("\x00\xff\x01\x23\x11\x22\x33\x44\x00")
31
+ # mydata.byte1 #=> 0
32
+ # mydata.byte2 #=> 255
33
+ # mydata.word #=> 0x0123
34
+ # mydata.dword #=> 0x44332211
35
+ # mydata.str #=> ""
12
36
  # @author LemonTree55
13
37
  module BinStruct
14
38
  # BinStruct error class
@@ -17,8 +41,9 @@ module BinStruct
17
41
  # Force binary encoding for +str+
18
42
  # @param [String] str
19
43
  # @return [String] binary encoded string
44
+ # @deprecated Use {::String#b} instead of this method
20
45
  def self.force_binary(str)
21
- str.dup.force_encoding(Encoding::BINARY)
46
+ str.b
22
47
  end
23
48
  end
24
49
 
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.3.0
4
+ version: 0.5.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-12-02 00:00:00.000000000 Z
11
+ date: 2025-02-17 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.
@@ -58,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 2.7.0
61
+ version: 3.0.0
62
62
  required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - ">="