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 +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
|
- - ">="
|