bin_struct 0.3.0 → 0.4.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: 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
  - - ">="