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 +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +83 -0
- data/lib/bin_struct/abstract_tlv.rb +80 -22
- data/lib/bin_struct/array.rb +62 -5
- data/lib/bin_struct/bit_attr.rb +18 -6
- data/lib/bin_struct/cstring.rb +8 -6
- data/lib/bin_struct/enum.rb +16 -13
- data/lib/bin_struct/int.rb +1 -1
- data/lib/bin_struct/int_string.rb +7 -0
- data/lib/bin_struct/length_from.rb +2 -2
- data/lib/bin_struct/oui.rb +3 -2
- data/lib/bin_struct/string.rb +40 -9
- data/lib/bin_struct/struct.rb +18 -9
- data/lib/bin_struct/version.rb +1 -1
- data/lib/bin_struct.rb +27 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6e644878c67b11f8006b130b0964a2349f0aa66645f2706cb97a20bf1ec6b77
|
4
|
+
data.tar.gz: 0bbf06e91de1bc410888f9c0e79f8caa0b7bb7f7082b5c775186c41c4a3d04db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
15
|
-
#
|
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
|
-
#
|
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
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
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 #=>
|
41
|
-
# tlv.value #=> '
|
42
|
-
# tlv.to_s #=> "\x00\x01\x00\
|
42
|
+
# tlv.length #=> 3
|
43
|
+
# tlv.value #=> '01:02:03'
|
44
|
+
# tlv.to_s #=> "\x00\x01\x00\x03\x01\x02\x03"
|
43
45
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
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
|
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
|
data/lib/bin_struct/array.rb
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
require 'forwardable'
|
10
10
|
|
11
11
|
module BinStruct
|
12
|
-
# @abstract Base class to define set of {
|
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
|
data/lib/bin_struct/bit_attr.rb
CHANGED
@@ -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
|
-
#
|
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,
|
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 [
|
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__ -
|
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__
|
data/lib/bin_struct/cstring.rb
CHANGED
@@ -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 [
|
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
|
-
|
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
|
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
|
data/lib/bin_struct/enum.rb
CHANGED
@@ -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
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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
|
data/lib/bin_struct/int.rb
CHANGED
@@ -55,7 +55,7 @@ module BinStruct
|
|
55
55
|
|
56
56
|
# @abstract
|
57
57
|
# @return [::String]
|
58
|
-
# @raise [Error] This is an
|
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 =
|
33
|
+
s = str.to_s.b
|
34
34
|
s[0, sz_to_read]
|
35
35
|
end
|
36
36
|
|
data/lib/bin_struct/oui.rb
CHANGED
@@ -8,10 +8,11 @@
|
|
8
8
|
|
9
9
|
module BinStruct
|
10
10
|
# OUI type, defined as a set of 3 bytes
|
11
|
-
#
|
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\
|
15
|
+
# oui.to_s # => "\x00\x01\x02".b
|
15
16
|
# @author Sylvain Daubert (2016-2024)
|
16
17
|
# @author LemonTree55
|
17
18
|
class OUI < Struct
|
data/lib/bin_struct/string.rb
CHANGED
@@ -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 <<
|
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
|
-
|
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
|
data/lib/bin_struct/struct.rb
CHANGED
@@ -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+
|
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
|
-
# * {.
|
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
|
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|
|
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
|
-
|
587
|
+
"-- #{title} #{'-' * (66 - title.length)}\n"
|
579
588
|
end
|
580
589
|
|
581
590
|
# @param [:Symbol] attr
|
data/lib/bin_struct/version.rb
CHANGED
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.
|
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.
|
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:
|
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:
|
61
|
+
version: 3.0.0
|
62
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
63
|
requirements:
|
64
64
|
- - ">="
|