bin_struct 0.3.0 → 0.5.0

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