bin_struct 0.2.0 → 0.3.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 +21 -0
- data/README.md +7 -1
- data/lib/bin_struct/bit_attr.rb +186 -0
- data/lib/bin_struct/cstring.rb +2 -1
- data/lib/bin_struct/int_string.rb +4 -4
- data/lib/bin_struct/string.rb +2 -1
- data/lib/bin_struct/struct.rb +113 -134
- data/lib/bin_struct/structable.rb +1 -1
- data/lib/bin_struct/version.rb +1 -1
- data/lib/bin_struct.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d43c8007a5ad33899e5c7ee6a09c306a64a95012bd489ee2d14d69c7e40dcf
|
4
|
+
data.tar.gz: db0b44a68cb8a23b1a5110cb12bb587c4cf4a737b42443f72c0ae9241fffa6c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 287f8dbf64a09b27b5fe376f9b6a982055713cdb2453ab7dd2b5ec0489678727441a3545427bb9f1f31d0ccc733bcc10e6af703fdc3b85b7cf539614b306bc21
|
7
|
+
data.tar.gz: cc3ba318afa6ac9706d57676256bbebeeec3b4dc52402728777b8c7a7fb89c9de8d893903b3e753d84a25aa6339b9aa919cfd4a223ff1f1a446a2d169047dee2
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,27 @@
|
|
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.3.0 - 2024-12-02
|
7
|
+
|
8
|
+
### Added
|
9
|
+
|
10
|
+
- `BitAddr` class is added. This class is used as a `Structable` type to handle bitfield attributes.
|
11
|
+
- Add `Struct.define_bit_attr`, `.define_bit_attr_before` and `.define_bit_attr_before` to define bitfield attributes.
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
- `Struct.define_bit_attr_on` is removed in favor of `Struct.define_bit_attr`. Bitfield attributes are now first class attributes, and no more an onverlay on `Int`.
|
16
|
+
|
17
|
+
## 0.2.1 - 2024-11-25
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- `CString` and `String` initializers now accepts `:value` option to set string initial value.
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- `IntString` initializer option `:string` is renamed into `:value`.
|
26
|
+
|
6
27
|
## 0.2.0 - 2024-07-21
|
7
28
|
|
8
29
|
### Changed
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[](https://badge.fury.io/rb/bin_struct)
|
2
|
+
[](https://github.com/lemontree55/bin_struct/actions/workflows/main.yml)
|
3
|
+
|
1
4
|
# BinStruct
|
2
5
|
|
3
6
|
BinStruct provides a simple way to create and dissect binary data. It is an extraction from [PacketGen](https://github.com/lemontree55/packetgen) 3.x Fields.
|
@@ -6,9 +9,12 @@ BinStruct provides a simple way to create and dissect binary data. It is an extr
|
|
6
9
|
|
7
10
|
Installation using RubyGems is easy:
|
8
11
|
|
9
|
-
|
12
|
+
```shell
|
13
|
+
gem install bin_struct
|
14
|
+
```
|
10
15
|
|
11
16
|
Or add it to a Gemfile:
|
17
|
+
|
12
18
|
```ruby
|
13
19
|
gem 'bin_struct'
|
14
20
|
```
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of BinStruct
|
4
|
+
# see https://github.com/lemontree55/bin_struct for more informations
|
5
|
+
# Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
require 'digest'
|
8
|
+
|
9
|
+
module BinStruct
|
10
|
+
# Define a bitfield attribute to embed in a {Struct}.
|
11
|
+
#
|
12
|
+
# class MyStruct < BinStruct::Struct
|
13
|
+
# # Create a 32-bit bitfield attribute, with fields a (16 bits), b and c (4 bits each) and d (8 bits).
|
14
|
+
# # a is the leftmost field in bitfield, and d the rightmost one.
|
15
|
+
# define_attr :int32, BinStruct::BitAttr.create(width: 32, a: 16, b: 4, c: 4, d:8)
|
16
|
+
# end
|
17
|
+
# @since 0.3.0
|
18
|
+
# @author LemonTree55
|
19
|
+
class BitAttr
|
20
|
+
include Structable
|
21
|
+
|
22
|
+
# @return [Integer] width in bits of bit attribute
|
23
|
+
attr_reader :width
|
24
|
+
# @return [Array[Symbol]]
|
25
|
+
attr_reader :bit_methods
|
26
|
+
|
27
|
+
# @private
|
28
|
+
Parameters = Struct.new(:width, :fields, :int)
|
29
|
+
|
30
|
+
class << self
|
31
|
+
@cache = {}
|
32
|
+
|
33
|
+
# @private
|
34
|
+
# @return [Parameters]
|
35
|
+
attr_reader :parameters
|
36
|
+
|
37
|
+
# Create a new {BitAttr} subclass with specified parameters
|
38
|
+
# @param [Integer] width size of bitfields in bits. Must be a size of an {Int} (8, 16, 24, 32 or 64 bits).
|
39
|
+
# @param [:big,:little,:native] endian endianess of bit attribute as an integer
|
40
|
+
# @param [Hash{Symbol=>Integer}] fields hash associating field names with their size. Total size MUST be equal
|
41
|
+
# to +width+.
|
42
|
+
# @return [Class]
|
43
|
+
# @raise [ArgumentError] raise if:
|
44
|
+
# * width is not a size of one of {Int} subclasses,
|
45
|
+
# * sum of bitfield sizes is not equal to +width+
|
46
|
+
def create(width:, endian: :big, **fields)
|
47
|
+
raise ArgumentError, 'with must be 8, 16, 24, 32 or 64' unless [8, 16, 24, 32, 64].include?(width)
|
48
|
+
|
49
|
+
hsh = compute_hash(width, endian, fields)
|
50
|
+
cached = cache[hsh]
|
51
|
+
return cached if cached
|
52
|
+
|
53
|
+
total_size = fields.reduce(0) { |acc, ary| acc + ary.last }
|
54
|
+
raise ArgumentError, "sum of bitfield sizes is not equal to #{width}" unless total_size == width
|
55
|
+
|
56
|
+
cache[hsh] = Class.new(self) do
|
57
|
+
int_klass = BinStruct.const_get("Int#{width}")
|
58
|
+
@parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# @return [Hash{::String=>Class}]
|
65
|
+
def cache
|
66
|
+
return @cache if defined? @cache
|
67
|
+
|
68
|
+
@cache = {}
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param [::Array] params
|
72
|
+
# @return [::String]
|
73
|
+
def compute_hash(*params)
|
74
|
+
Digest::MD5.digest(Marshal.dump(params))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Initialize bit attribute
|
79
|
+
# @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
|
80
|
+
# initialization values
|
81
|
+
# @return [self]
|
82
|
+
def initialize(opts = {})
|
83
|
+
parameters = self.class.parameters
|
84
|
+
raise NotImplementedError, '#initialize may only be called on subclass of {self.class}' if parameters.nil?
|
85
|
+
|
86
|
+
@width = parameters.width
|
87
|
+
@fields = parameters.fields
|
88
|
+
@int = parameters.int.dup
|
89
|
+
@data = {}
|
90
|
+
@bit_methods = []
|
91
|
+
|
92
|
+
parameters.fields.each do |name, size|
|
93
|
+
@data[name] = opts[name] || 0
|
94
|
+
define_methods(name, size)
|
95
|
+
end
|
96
|
+
@bit_methods.freeze
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get type name
|
100
|
+
# @return [::String]
|
101
|
+
def type_name
|
102
|
+
return @type_name if defined? @type_name
|
103
|
+
|
104
|
+
endian_suffix = case @int.endian
|
105
|
+
when :big then ''
|
106
|
+
when :little then 'le'
|
107
|
+
when :native then 'n'
|
108
|
+
end
|
109
|
+
@type_name = "BitAttr#{@width}#{endian_suffix}"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Populate bit attribute from +str+
|
113
|
+
# @param [::String,nil] str
|
114
|
+
# @return [self]
|
115
|
+
def read(str)
|
116
|
+
return self if str.nil?
|
117
|
+
|
118
|
+
@int.read(str)
|
119
|
+
compute_data(@int.to_i)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Give integer associated to this attribute
|
123
|
+
# @return [Integer]
|
124
|
+
def to_i
|
125
|
+
v = 0
|
126
|
+
@fields.each do |name, size|
|
127
|
+
v <<= size
|
128
|
+
v |= @data[name]
|
129
|
+
end
|
130
|
+
|
131
|
+
v
|
132
|
+
end
|
133
|
+
alias to_human to_i
|
134
|
+
|
135
|
+
# Return binary string
|
136
|
+
# @return [::String]
|
137
|
+
def to_s
|
138
|
+
@int.value = to_i
|
139
|
+
@int.to_s
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set fields from associated integer
|
143
|
+
# @param [#to_i] value
|
144
|
+
# @return [self]
|
145
|
+
def from_human(value)
|
146
|
+
compute_data(value.to_i)
|
147
|
+
end
|
148
|
+
|
149
|
+
def format_inspect
|
150
|
+
str = @int.format_inspect << "\n"
|
151
|
+
str << @data.map { |name, value| "#{name}:#{value}" }.join(' ')
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# @param [Integer] value
|
157
|
+
# @return [self]
|
158
|
+
def compute_data(value)
|
159
|
+
@fields.reverse_each do |name, size|
|
160
|
+
@data[name] = value & ((2**size) - 1)
|
161
|
+
value >>= size
|
162
|
+
end
|
163
|
+
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param [Symbol] name
|
168
|
+
# @return [void]
|
169
|
+
def define_methods(name, size)
|
170
|
+
instance_eval "def #{name}; @data[#{name.inspect}]; end\n", __FILE__, __LINE__ # def name; data[:name]; end
|
171
|
+
bit_methods << name
|
172
|
+
bit_methods << :"#{name}="
|
173
|
+
|
174
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
175
|
+
if size == 1
|
176
|
+
instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
|
177
|
+
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
|
179
|
+
bit_methods << :"#{name}?"
|
180
|
+
else
|
181
|
+
instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
|
182
|
+
end
|
183
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/bin_struct/cstring.rb
CHANGED
@@ -76,8 +76,9 @@ 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
80
|
def initialize(options = {})
|
80
|
-
register_internal_string(+'')
|
81
|
+
register_internal_string(options[:value] || +'')
|
81
82
|
@static_length = options[:static_length]
|
82
83
|
end
|
83
84
|
|
@@ -20,9 +20,9 @@ module BinStruct
|
|
20
20
|
|
21
21
|
# @param [Hash] options
|
22
22
|
# @option options [Class] :length_type should be a {Int} subclass. Default to {Int8}.
|
23
|
-
# @option options [::String] :
|
23
|
+
# @option options [::String] :value String value. Default to +""+
|
24
24
|
def initialize(options = {})
|
25
|
-
@string = BinStruct::String.new.read(options[:
|
25
|
+
@string = BinStruct::String.new.read(options[:value] || +'')
|
26
26
|
@length = (options[:length_type] || Int8).new
|
27
27
|
calc_length
|
28
28
|
end
|
@@ -61,7 +61,7 @@ module BinStruct
|
|
61
61
|
# @return [::String]
|
62
62
|
def string=(str)
|
63
63
|
@length.value = str.to_s.size
|
64
|
-
@string
|
64
|
+
@string.read(str)
|
65
65
|
end
|
66
66
|
|
67
67
|
# Get binary string
|
@@ -82,7 +82,7 @@ module BinStruct
|
|
82
82
|
# Get human readable string
|
83
83
|
# @return [::String]
|
84
84
|
def to_human
|
85
|
-
@string
|
85
|
+
@string.to_s
|
86
86
|
end
|
87
87
|
|
88
88
|
# Set length from internal string length
|
data/lib/bin_struct/string.rb
CHANGED
@@ -32,8 +32,9 @@ 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
36
|
def initialize(options = {})
|
36
|
-
register_internal_string(+'')
|
37
|
+
register_internal_string(options[:value] || +'')
|
37
38
|
initialize_length_from(options)
|
38
39
|
@static_length = options[:static_length]
|
39
40
|
end
|
data/lib/bin_struct/struct.rb
CHANGED
@@ -81,25 +81,23 @@ module BinStruct
|
|
81
81
|
# define_attr :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }
|
82
82
|
#
|
83
83
|
# == Generating bit attributes
|
84
|
-
# {.
|
85
|
-
#
|
86
|
-
# define_attr :frag, BinStruct::Int16, default: 0
|
87
|
-
# define_bit_attr_on :frag, :flag_rsv, :flag_df, :flag_mf, :fragment_offset, 13
|
84
|
+
# {.define_bit_attr} creates a bit attribute. For example, +frag+ attribute in IP header:
|
85
|
+
# define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13
|
88
86
|
#
|
89
87
|
# This example generates methods:
|
90
88
|
# * +#frag+ and +#frag=+ to access +frag+ attribute as a 16-bit integer,
|
91
89
|
# * +#flag_rsv?+, +#flag_rsv=+, +#flag_df?+, +#flag_df=+, +#flag_mf?+ and +#flag_mf=+
|
92
90
|
# to access Boolean RSV, MF and DF flags from +frag+ attribute,
|
91
|
+
# * +#flag_rsv+, +#flag_df+ and +#flag_mf# to read RSV, MF and DF flags as Integer,
|
93
92
|
# * +#fragment_offset+ and +#fragment_offset=+ to access 13-bit integer fragment
|
94
93
|
# offset subattribute from +frag+ attribute.
|
95
94
|
#
|
96
95
|
# == Creating a new Struct class from another one
|
97
96
|
# Some methods may help in this case:
|
98
|
-
# * {.define_attr_before} to define a new attribute before an existing one,
|
99
|
-
# * {.define_attr_after} to define a new attribute after an existing onr,
|
100
|
-
# * {.
|
101
|
-
# * {.
|
102
|
-
# * {.remove_bit_attrs_on} to remove bit attribute definition.
|
97
|
+
# * {.define_attr_before} and {.define_bit_attr_before} to define a new attribute before an existing one,
|
98
|
+
# * {.define_attr_after} and {.define_bit_attr_after} to define a new attribute after an existing onr,
|
99
|
+
# * {.remove_attr} to remove an existing attribute,
|
100
|
+
# * {.uptade_attr} to change options of an attribute (but not its type),
|
103
101
|
#
|
104
102
|
# @author Sylvain Daubert (2016-2024)
|
105
103
|
# @author LemonTree55
|
@@ -121,7 +119,7 @@ module BinStruct
|
|
121
119
|
# @return [Hash]
|
122
120
|
attr_reader :attr_defs
|
123
121
|
# Get bit attribute defintions for this class
|
124
|
-
# @return [Hash]
|
122
|
+
# @return [Hash{Symbol=>Array[Symbol]}]
|
125
123
|
attr_reader :bit_attrs
|
126
124
|
|
127
125
|
# On inheritage, create +@attr_defs+ class variable
|
@@ -198,11 +196,7 @@ module BinStruct
|
|
198
196
|
define_attr name, type, options
|
199
197
|
return if other.nil?
|
200
198
|
|
201
|
-
|
202
|
-
idx = attributes.index(other)
|
203
|
-
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
204
|
-
|
205
|
-
attributes[idx, 0] = name
|
199
|
+
move_attr(name, before: other)
|
206
200
|
end
|
207
201
|
|
208
202
|
# Define an attribute, after another one
|
@@ -217,11 +211,7 @@ module BinStruct
|
|
217
211
|
define_attr name, type, options
|
218
212
|
return if other.nil?
|
219
213
|
|
220
|
-
|
221
|
-
idx = attributes.index(other)
|
222
|
-
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
223
|
-
|
224
|
-
attributes[idx + 1, 0] = name
|
214
|
+
move_attr(name, after: other)
|
225
215
|
end
|
226
216
|
|
227
217
|
# Remove a previously defined attribute
|
@@ -229,9 +219,12 @@ module BinStruct
|
|
229
219
|
# @return [void]
|
230
220
|
def remove_attr(name)
|
231
221
|
attributes.delete(name)
|
232
|
-
|
222
|
+
attr_def = attr_defs.delete(name)
|
233
223
|
undef_method name if method_defined?(name)
|
234
224
|
undef_method :"#{name}=" if method_defined?(:"#{name}=")
|
225
|
+
return unless bit_attrs[name]
|
226
|
+
|
227
|
+
attr_def.type.new.bit_methods.each { |meth| undef_method(meth) }
|
235
228
|
end
|
236
229
|
|
237
230
|
# Update a previously defined attribute
|
@@ -250,64 +243,95 @@ module BinStruct
|
|
250
243
|
attr_defs[name].options.merge!(options)
|
251
244
|
end
|
252
245
|
|
253
|
-
# Define a bit attribute
|
246
|
+
# Define a bit attribute
|
254
247
|
# class MyHeader < BinStruct::Struct
|
255
|
-
#
|
256
|
-
# # define a bit attribute on :flag attribute
|
248
|
+
# # define a 16-bit attribute named :flag
|
257
249
|
# # flag1, flag2 and flag3 are 1-bit attributes
|
258
|
-
# # type and stype are 3-bit attributes. reserved is a
|
259
|
-
#
|
250
|
+
# # type and stype are 3-bit attributes. reserved is a 7-bit attribute
|
251
|
+
# define_bit_attr :flags, flag1: 1, flag2: 1, flag3: 1, type: 3, stype: 3, reserved: 7
|
260
252
|
# end
|
261
|
-
# A bit attribute of size 1 bit defines
|
262
|
-
# * +#attr+ which returns
|
263
|
-
# * +#attr
|
264
|
-
#
|
253
|
+
# A bit attribute of size 1 bit defines 3 methods:
|
254
|
+
# * +#attr+ which returns an Integer,
|
255
|
+
# * +#attr?+ which returns a Boolean,
|
256
|
+
# * +#attr=+ which accepts an Integer or a Boolean.
|
257
|
+
# A bit attribute of more bits defines only 2 methods:
|
265
258
|
# * +#attr+ which returns an Integer,
|
266
|
-
# * +#attr=+ which takes
|
267
|
-
# @param [Symbol] attr attribute name
|
268
|
-
#
|
269
|
-
# @param [
|
270
|
-
#
|
271
|
-
# @raise [ArgumentError] unknown +attr+
|
259
|
+
# * +#attr=+ which takes an Integer.
|
260
|
+
# @param [Symbol] attr attribute name
|
261
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
262
|
+
# @param [Integer] default default value for whole attribute
|
263
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
272
264
|
# @return [void]
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
265
|
+
# @since 0.3.0
|
266
|
+
def define_bit_attr(attr, endian: :big, default: 0, **fields)
|
267
|
+
width = fields.reduce(0) { |acc, ary| acc + ary.last }
|
268
|
+
bit_attr_klass = BitAttr.create(width: width, endian: endian, **fields)
|
269
|
+
define_attr(attr, bit_attr_klass, default: default)
|
270
|
+
fields.each_key { |field| register_bit_attr_field(attr, field) }
|
271
|
+
bit_attr_klass.new.bit_methods.each do |meth|
|
272
|
+
if meth.to_s.end_with?('=')
|
273
|
+
define_method(meth) { |value| self[attr].send(meth, value) }
|
274
|
+
else
|
275
|
+
define_method(meth) { self[attr].send(meth) }
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
281
279
|
|
282
|
-
|
283
|
-
|
284
|
-
|
280
|
+
# Define a bit attribute, before another attribute
|
281
|
+
# @param [Symbol,nil] other attribute name to create a new one before.
|
282
|
+
# If +nil+, new attribute is appended.
|
283
|
+
# @param [Symbol] name attribute name to create
|
284
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
285
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
286
|
+
# @return [void]
|
287
|
+
# @since 0.3.0
|
288
|
+
# @see .define_bit_attr
|
289
|
+
def define_bit_attr_before(other, name, endian: :big, **fields)
|
290
|
+
define_bit_attr(name, endian: endian, **fields)
|
291
|
+
return if other.nil?
|
285
292
|
|
286
|
-
|
293
|
+
move_attr(name, before: other)
|
294
|
+
end
|
287
295
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
296
|
+
# Define a bit attribute after another attribute
|
297
|
+
# @param [Symbol,nil] other attribute name to create a new one after.
|
298
|
+
# If +nil+, new attribute is appended.
|
299
|
+
# @param [Symbol] name attribute name to create
|
300
|
+
# @param [:big,:little,:native] endian endianess of Integer
|
301
|
+
# @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
|
302
|
+
# @return [void]
|
303
|
+
# @since 0.3.0
|
304
|
+
# @see .define_bit_attr
|
305
|
+
def define_bit_attr_after(other, name, endian: :big, **fields)
|
306
|
+
define_bit_attr(name, endian: endian, **fields)
|
307
|
+
return if other.nil?
|
292
308
|
|
293
|
-
|
294
|
-
end
|
309
|
+
move_attr(name, after: other)
|
295
310
|
end
|
296
311
|
|
297
|
-
|
298
|
-
|
312
|
+
private
|
313
|
+
|
314
|
+
# @param [Symbol] name
|
315
|
+
# @param [Symbol,nil] before
|
316
|
+
# @param [Symbol,nil] after
|
299
317
|
# @return [void]
|
300
|
-
|
301
|
-
|
302
|
-
|
318
|
+
# @raise [ArgumentError] Both +before+ and +after+ are nil, or both are set.
|
319
|
+
def move_attr(name, before: nil, after: nil)
|
320
|
+
move_check_destination(before, after)
|
303
321
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
322
|
+
other = before || after
|
323
|
+
attributes.delete(name)
|
324
|
+
idx = attributes.index(other)
|
325
|
+
raise ArgumentError, "unknown #{other} attribute" if idx.nil?
|
326
|
+
|
327
|
+
idx += 1 unless after.nil?
|
328
|
+
attributes[idx, 0] = name
|
308
329
|
end
|
309
330
|
|
310
|
-
|
331
|
+
def move_check_destination(before, after)
|
332
|
+
raise ArgumentError 'one of before: and after: arguments MUST be set' if before.nil? && after.nil?
|
333
|
+
raise ArgumentError 'only one of before and after argument MUST be set' if !before.nil? && !after.nil?
|
334
|
+
end
|
311
335
|
|
312
336
|
def add_methods(name, type)
|
313
337
|
define = []
|
@@ -328,73 +352,15 @@ module BinStruct
|
|
328
352
|
class_eval define.join("\n")
|
329
353
|
end
|
330
354
|
|
331
|
-
def
|
332
|
-
|
333
|
-
|
334
|
-
if size == 1
|
335
|
-
add_single_bit_methods(attr, name, size, total_size, shift)
|
336
|
-
else
|
337
|
-
add_multibit_methods(attr, name, size, total_size, shift)
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
def compute_mask(size, shift)
|
342
|
-
((2**size) - 1) << shift
|
343
|
-
end
|
344
|
-
|
345
|
-
def compute_clear_mask(total_size, mask)
|
346
|
-
((2**total_size) - 1) & (~mask & ((2**total_size) - 1))
|
347
|
-
end
|
348
|
-
|
349
|
-
def add_single_bit_methods(attr, name, size, total_size, shift)
|
350
|
-
mask = compute_mask(size, shift)
|
351
|
-
clear_mask = compute_clear_mask(total_size, mask)
|
352
|
-
|
353
|
-
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
354
|
-
def #{name}? # def bit?
|
355
|
-
val = (self[:#{attr}].to_i & #{mask}) >> #{shift} # val = (self[:attr}].to_i & 1}) >> 1
|
356
|
-
val != 0 # val != 0
|
357
|
-
end # end
|
358
|
-
def #{name}=(v) # def bit=(v)
|
359
|
-
val = v ? 1 : 0 # val = v ? 1 : 0
|
360
|
-
self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfffd
|
361
|
-
self[:#{attr}].value |= val << #{shift} # self[:attr].value |= val << 1
|
362
|
-
end # end
|
363
|
-
METHODS
|
364
|
-
end
|
365
|
-
|
366
|
-
def add_multibit_methods(attr, name, size, total_size, shift)
|
367
|
-
mask = compute_mask(size, shift)
|
368
|
-
clear_mask = compute_clear_mask(total_size, mask)
|
369
|
-
|
370
|
-
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
371
|
-
def #{name} # def multibit
|
372
|
-
(self[:#{attr}].to_i & #{mask}) >> #{shift} # (self[:attr].to_i & 6) >> 1
|
373
|
-
end # end
|
374
|
-
def #{name}=(v) # def multibit=(v)
|
375
|
-
self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfff9
|
376
|
-
self[:#{attr}].value |= (v & #{(2**size) - 1}) << #{shift} # self[:attr].value |= (v & 3) << 1
|
377
|
-
end # end
|
378
|
-
METHODS
|
379
|
-
end
|
380
|
-
|
381
|
-
def register_bit_attr_size(attr, name, size)
|
382
|
-
bit_attrs[attr] = {} if bit_attrs[attr].nil?
|
383
|
-
bit_attrs[attr][name] = size
|
355
|
+
def register_bit_attr_field(attr, field)
|
356
|
+
bit_attrs[attr] ||= []
|
357
|
+
bit_attrs[attr] << field
|
384
358
|
end
|
385
359
|
|
386
360
|
def attr_defs_property_from(attr, property, options)
|
387
361
|
attr_defs[attr].send(:"#{property}=", options.delete(property)) if options.key?(property)
|
388
362
|
end
|
389
363
|
|
390
|
-
def size_from(args)
|
391
|
-
if args.first.is_a? Integer
|
392
|
-
args.shift
|
393
|
-
else
|
394
|
-
1
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
364
|
def check_existence_of(attr)
|
399
365
|
raise ArgumentError, "unknown #{attr} attribute for #{self}" unless attr_defs.key?(attr)
|
400
366
|
end
|
@@ -402,7 +368,7 @@ module BinStruct
|
|
402
368
|
|
403
369
|
# Create a new Struct object
|
404
370
|
# @param [Hash] options Keys are symbols. They should have name of object
|
405
|
-
# attributes, as defined by {.define_attr} and by {.
|
371
|
+
# attributes, as defined by {.define_attr} and by {.define_bit_attr}.
|
406
372
|
def initialize(options = {})
|
407
373
|
@attributes = {}
|
408
374
|
@optional_attributes = {}
|
@@ -413,8 +379,8 @@ module BinStruct
|
|
413
379
|
initialize_optional(attr)
|
414
380
|
end
|
415
381
|
|
416
|
-
self.class.bit_attrs.each_value do |
|
417
|
-
|
382
|
+
self.class.bit_attrs.each_value do |bit_fields|
|
383
|
+
bit_fields.each do |bit|
|
418
384
|
send(:"#{bit}=", options[bit]) if options[bit]
|
419
385
|
end
|
420
386
|
end
|
@@ -599,26 +565,39 @@ module BinStruct
|
|
599
565
|
end
|
600
566
|
end
|
601
567
|
|
568
|
+
# @param [Symbol] attr
|
569
|
+
# @return [void]
|
602
570
|
def initialize_optional(attr)
|
603
571
|
optional = attr_defs[attr].optional
|
604
572
|
@optional_attributes[attr] = optional if optional
|
605
573
|
end
|
606
574
|
|
575
|
+
# @return [String]
|
607
576
|
def inspect_titleize
|
608
577
|
title = self.class.to_s
|
609
578
|
+"-- #{title} #{'-' * (66 - title.length)}\n"
|
610
579
|
end
|
611
580
|
|
581
|
+
# @param [:Symbol] attr
|
582
|
+
# @param [Structable] value
|
583
|
+
# @param [Integer] level
|
584
|
+
# @return [::String]
|
612
585
|
def inspect_attribute(attr, value, level = 1)
|
613
|
-
type = value.class.to_s.sub(/.*::/, '')
|
614
|
-
inspect_format(type, attr, value.format_inspect, level)
|
615
|
-
end
|
616
|
-
|
617
|
-
def inspect_format(type, attr, value, level = 1)
|
618
586
|
str = inspect_shift_level(level)
|
619
|
-
|
587
|
+
value_lines = value.format_inspect.split("\n")
|
588
|
+
str << (FMT_ATTR % [value.type_name, attr, value_lines.shift])
|
589
|
+
return str if value_lines.empty?
|
590
|
+
|
591
|
+
shift = (FMT_ATTR % ['', '', 'START']).index('START')
|
592
|
+
value_lines.each do |l|
|
593
|
+
str << inspect_shift_level(level)
|
594
|
+
str << (' ' * shift) << l << "\n"
|
595
|
+
end
|
596
|
+
str
|
620
597
|
end
|
621
598
|
|
599
|
+
# @param [Integer] level
|
600
|
+
# @return [String]
|
622
601
|
def inspect_shift_level(level = 1)
|
623
602
|
' ' * (level + 1)
|
624
603
|
end
|
data/lib/bin_struct/version.rb
CHANGED
data/lib/bin_struct.rb
CHANGED
@@ -25,6 +25,7 @@ end
|
|
25
25
|
require_relative 'bin_struct/structable'
|
26
26
|
require_relative 'bin_struct/int'
|
27
27
|
require_relative 'bin_struct/enum'
|
28
|
+
require_relative 'bin_struct/bit_attr'
|
28
29
|
require_relative 'bin_struct/struct'
|
29
30
|
require_relative 'bin_struct/length_from'
|
30
31
|
require_relative 'bin_struct/abstract_tlv'
|
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.3.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-
|
11
|
+
date: 2024-12-02 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.
|
@@ -26,6 +26,7 @@ files:
|
|
26
26
|
- lib/bin_struct.rb
|
27
27
|
- lib/bin_struct/abstract_tlv.rb
|
28
28
|
- lib/bin_struct/array.rb
|
29
|
+
- lib/bin_struct/bit_attr.rb
|
29
30
|
- lib/bin_struct/cstring.rb
|
30
31
|
- lib/bin_struct/enum.rb
|
31
32
|
- lib/bin_struct/int.rb
|