bin_struct 0.3.0 → 0.4.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: aeb290e624ce4004c20cfaf40fc60ae43938c80eab8fd1df1e8aa908a75e26cf
4
+ data.tar.gz: 8cb5dd0abdce7421b8269a4d35c14886f57cc6440a50c924b0937e5b30fe20ce
5
5
  SHA512:
6
- metadata.gz: 287f8dbf64a09b27b5fe376f9b6a982055713cdb2453ab7dd2b5ec0489678727441a3545427bb9f1f31d0ccc733bcc10e6af703fdc3b85b7cf539614b306bc21
7
- data.tar.gz: cc3ba318afa6ac9706d57676256bbebeeec3b4dc52402728777b8c7a7fb89c9de8d893903b3e753d84a25aa6339b9aa919cfd4a223ff1f1a446a2d169047dee2
6
+ metadata.gz: ecf6acc2f5c406556598212d22ad6257d22ed646c1128a145cff1730dc537bb7acfd40903f211aad28b11fa30c11324a98b98ecebb70ad7b84ea991bcf181f25
7
+ data.tar.gz: 1dcfbb20a54f7c08d2794e0e8391b70c633d65f6ccbc41c372e46c77da6fb036fbfa9d1548462030b86603e3e41b132019da70b3c2d603f7f21116bdc842ec14
data/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
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.4.0 - 2025-02-13
7
+
8
+ ### Added
9
+
10
+ - Add `Struct#attribute?` to check existence of an attribute.
11
+ - Add `AbstractTLV.derive` to derive a new subclass from a concrete TLV class.
12
+
13
+ ### Fixed
14
+
15
+ - Update and fix Yard documentation.
16
+
6
17
  ## 0.3.0 - 2024-12-02
7
18
 
8
19
  ### 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).
@@ -13,7 +13,7 @@ module BinStruct
13
13
  #
14
14
  # ===Usage
15
15
  # To simply define a new TLV class, do:
16
- # MyTLV = PacketGen::Types::AbstractTLV.create
16
+ # MyTLV = BinStruct::AbstractTLV.create
17
17
  # MyTLV.define_type_enum 'one' => 1, 'two' => 2
18
18
  # This will define a new +MyTLV+ class, subclass of {AbstractTLV}. This class will
19
19
  # define 3 attributes:
@@ -32,9 +32,9 @@ module BinStruct
32
32
  #
33
33
  # ===Advanced usage
34
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)
35
+ # MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
36
+ # length_class: BinStruct::Int16,
37
+ # value_class: PacketGen::Header::IP::Addr)
38
38
  # tlv = MyTLV.new(type: 1, value: '1.2.3.4')
39
39
  # tlv.type #=> 1
40
40
  # tlv.length #=> 4
@@ -43,9 +43,9 @@ module BinStruct
43
43
  #
44
44
  # Some aliases may also be defined. For example, to create a TLV type
45
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
+ # MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
47
+ # length_class: BinStruct::Int16,
48
+ # aliases: { code: :type })
49
49
  # tlv = MyTLV.new(code: 1, value: 'abcd')
50
50
  # tlv.code #=> 1
51
51
  # tlv.type #=> 1
@@ -77,11 +77,12 @@ module BinStruct
77
77
  # in the desired order.
78
78
  # @param [::String] attr_in_length give attributes to compute length on.
79
79
  # @return [Class]
80
+ # @raise [Error] Called on {AbstractTLV} subclass
80
81
  def create(type_class: Int8Enum, length_class: Int8, value_class: String,
81
82
  aliases: {}, attr_order: 'TLV', attr_in_length: 'V')
82
83
  unless equal?(AbstractTLV)
83
84
  raise Error,
84
- '.create cannot be called on a subclass of PacketGen::Types::AbstractTLV'
85
+ '.create cannot be called on a subclass of BinStruct::AbstractTLV'
85
86
  end
86
87
 
87
88
  klass = Class.new(self)
@@ -91,7 +92,7 @@ module BinStruct
91
92
  check_attr_in_length(attr_in_length)
92
93
  check_attr_order(attr_order)
93
94
  generate_attributes(klass, attr_order, type_class, length_class, value_class)
94
-
95
+ generate_aliases_for(klass, aliases)
95
96
  aliases.each do |al, orig|
96
97
  klass.instance_eval do
97
98
  alias_method al, orig if klass.method_defined?(orig)
@@ -103,6 +104,50 @@ module BinStruct
103
104
  end
104
105
  # rubocop:enable Metrics/ParameterLists
105
106
 
107
+ # On inheritage, copy aliases and attr_in_length
108
+ # @param [Class] klass inheriting class
109
+ # @return [void]
110
+ # @since 0.4.0
111
+ # @author LemonTree55
112
+ def inherited(klass)
113
+ super
114
+
115
+ aliases = @aliases.clone
116
+ attr_in_length = @attr_in_length.clone
117
+
118
+ klass.class_eval do
119
+ @aliases = aliases
120
+ @attr_in_length = attr_in_length
121
+ end
122
+ end
123
+
124
+ # Derive a new TLV class from an existing one
125
+ # @param [Class,nil] type_class New class to use for +type+. Unchanged if +nil+.
126
+ # @param [Class,nil] length_class New class to use for +length+. Unchanged if +nil+.
127
+ # @param [Class,nil] value_class New class to use for +value+. Unchanged if +nil+.
128
+ # @return [Class]
129
+ # @raise [Error] Called on {AbstractTLV} class
130
+ # @since 0.4.0
131
+ # @author LemonTree55
132
+ # @example
133
+ # # TLV with type and length on 16 bits, value is a BinStruct::String
134
+ # FirstTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16, length_class: BinStruct::Int16)
135
+ # # TLV with same type and length classes than FirstTLV, but value is an array of Int8
136
+ # SecondTLV = FirstTLV.derive(value_class: BinStruct::ArrayOfInt8)
137
+ def derive(type_class: nil, length_class: nil, value_class: nil, aliases: {})
138
+ raise Error, ".derive cannot be called on #{name}" if equal?(AbstractTLV)
139
+
140
+ klass = Class.new(self)
141
+ klass.aliases.merge!(aliases)
142
+ generate_aliases_for(klass, aliases)
143
+
144
+ klass.attr_defs[:type].type = type_class unless type_class.nil?
145
+ klass.attr_defs[:length].type = length_class unless length_class.nil?
146
+ klass.attr_defs[:value].type = value_class unless value_class.nil?
147
+
148
+ klass
149
+ end
150
+
106
151
  # @!attribute type
107
152
  # @abstract
108
153
  # Type attribute for real TLV class
@@ -168,6 +213,15 @@ module BinStruct
168
213
  end
169
214
  end
170
215
  end
216
+
217
+ def generate_aliases_for(klass, aliases)
218
+ aliases.each do |al, orig|
219
+ klass.instance_eval do
220
+ alias_method al, orig if klass.method_defined?(orig)
221
+ alias_method :"#{al}=", :"#{orig}=" if klass.method_defined?(:"#{orig}=")
222
+ end
223
+ end
224
+ end
171
225
  end
172
226
 
173
227
  # @!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]
@@ -112,6 +113,7 @@ module BinStruct
112
113
 
113
114
  # Clear array. Reset associated counter, if any.
114
115
  # @return [void]
116
+ # @see #clear
115
117
  def clear!
116
118
  @array.clear
117
119
  @counter&.from_human(0)
@@ -137,7 +139,8 @@ module BinStruct
137
139
 
138
140
  # @abstract depend on private method +#record_from_hash+ which should be
139
141
  # declared by subclasses.
140
- # Add an object to this array. Do not update associated counter.
142
+ # Add an object to this array. Do not update associated counter. If associated must be incremented, use
143
+ # {#<<}
141
144
  # @param [Object] obj type depends on subclass
142
145
  # @return [self]
143
146
  # @see #<<
@@ -154,9 +157,11 @@ module BinStruct
154
157
 
155
158
  # @abstract depend on private method +#record_from_hash+ which should be
156
159
  # declared by subclasses.
157
- # Add an object to this array, and increment associated counter, if any
160
+ # Add an object to this array, and increment associated counter, if any. If associated counter must not be
161
+ # incremented, use {#push}.
158
162
  # @param [Object] obj type depends on subclass
159
163
  # @return [self]
164
+ # @see #push
160
165
  def <<(obj)
161
166
  push(obj)
162
167
  @counter&.from_human(@counter.to_i + 1)
@@ -15,13 +15,14 @@ module BinStruct
15
15
  # define_attr :int32, BinStruct::BitAttr.create(width: 32, a: 16, b: 4, c: 4, d:8)
16
16
  # end
17
17
  # @since 0.3.0
18
+ # @abstract Subclasses must de derived using {.create}.
18
19
  # @author LemonTree55
19
20
  class BitAttr
20
21
  include Structable
21
22
 
22
23
  # @return [Integer] width in bits of bit attribute
23
24
  attr_reader :width
24
- # @return [Array[Symbol]]
25
+ # @return [::Array[Symbol]]
25
26
  attr_reader :bit_methods
26
27
 
27
28
  # @private
@@ -79,9 +80,10 @@ module BinStruct
79
80
  # @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
80
81
  # initialization values
81
82
  # @return [self]
83
+ # @raise [NotImplementedError] raised when called on {BitAttr} class
82
84
  def initialize(opts = {})
83
85
  parameters = self.class.parameters
84
- raise NotImplementedError, '#initialize may only be called on subclass of {self.class}' if parameters.nil?
86
+ raise NotImplementedError, "#initialize may only be called on subclass of #{self.class}" if parameters.nil?
85
87
 
86
88
  @width = parameters.width
87
89
  @fields = parameters.fields
@@ -110,7 +112,7 @@ module BinStruct
110
112
  end
111
113
 
112
114
  # Populate bit attribute from +str+
113
- # @param [::String,nil] str
115
+ # @param [#to_s,nil] str
114
116
  # @return [self]
115
117
  def read(str)
116
118
  return self if str.nil?
@@ -175,7 +177,7 @@ module BinStruct
175
177
  if size == 1
176
178
  instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
177
179
  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
180
+ "@data[#{name.inspect}] = v; end", __FILE__, __LINE__ - 1
179
181
  bit_methods << :"#{name}?"
180
182
  else
181
183
  instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
@@ -76,14 +76,14 @@ module BinStruct
76
76
 
77
77
  # @param [Hash] options
78
78
  # @option options [Integer] :static_length set a static length for this string
79
- # @option options [::String] :value string value (default to +''+)
79
+ # @option options [::String] :value string value (default to +""+)
80
80
  def initialize(options = {})
81
81
  register_internal_string(options[:value] || +'')
82
82
  @static_length = options[:static_length]
83
83
  end
84
84
 
85
85
  # Populate self from binary string
86
- # @param [::String] str
86
+ # @param [#to_s] str
87
87
  # @return [self]
88
88
  def read(str)
89
89
  s = str.to_s
@@ -13,19 +13,20 @@ module BinStruct
13
13
  # and named values.
14
14
  #
15
15
  # == Simple example
16
- # enum = Int8Enum.new('low' => 0, 'medium' => 1, 'high' => 2})
16
+ # enum = Int8Enum.new('low' => 0, 'medium' => 1, 'high' => 2})
17
17
  # In this example, +enum+ is a 8-bit attribute which may take one
18
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
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 name will raise an exception:
25
+ # enum.value = 'unknown' # => raise!
26
+ # But {#read} and {#value=} will not raise when reading/setting an out-of-bound integer. This
28
27
  # to enable decoding (or forging) of bad packets.
28
+ # enum.read("\x05".b).value # => 5
29
+ # enum.value = 4 # => 4
29
30
  # @author Sylvain Daubert (2016-2024)
30
31
  # @author LemonTree55
31
32
  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
@@ -32,7 +32,7 @@ module BinStruct
32
32
  # @option options [Int,Proc] :length_from object or proc from which
33
33
  # takes length when reading
34
34
  # @option options [Integer] :static_length set a static length for this string
35
- # @option options [::String] :value string value (default to +''+)
35
+ # @option options [::String] :value string value (default to +""+)
36
36
  def initialize(options = {})
37
37
  register_internal_string(options[:value] || +'')
38
38
  initialize_length_from(options)
@@ -47,7 +47,7 @@ module BinStruct
47
47
  end
48
48
 
49
49
  # Populate String from a binary String. Limit length using {LengthFrom} or {#static_length}, if one is set.
50
- # @param [::String] str
50
+ # @param [::String,nil] str
51
51
  # @return [self]
52
52
  def read(str)
53
53
  s = read_with_length_from(str)
@@ -87,7 +87,7 @@ module BinStruct
87
87
  self
88
88
  end
89
89
 
90
- # Generate binary string
90
+ # Generate "binary" string
91
91
  # @return [::String]
92
92
  def to_s
93
93
  @string
@@ -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
@@ -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.4.0'
12
12
  end
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.4.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-13 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
  - - ">="