bin_struct 0.4.0 → 0.5.1
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/lib/bin_struct/abstract_tlv.rb +18 -14
- data/lib/bin_struct/array.rb +52 -0
- data/lib/bin_struct/bit_attr.rb +60 -29
- data/lib/bin_struct/cstring.rb +6 -4
- data/lib/bin_struct/enum.rb +11 -9
- data/lib/bin_struct/length_from.rb +2 -2
- data/lib/bin_struct/oui.rb +3 -2
- data/lib/bin_struct/string.rb +37 -6
- data/lib/bin_struct/struct.rb +15 -13
- data/lib/bin_struct/version.rb +1 -1
- data/lib/bin_struct.rb +27 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7d87b0168273fd5ed8cb81c89ecbc1726d93544bf70d57467490d5732033ebc
|
4
|
+
data.tar.gz: 5f14f873d4d45ee090b81033a952d62e3673334444b61a62825c9925d9574af6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f82248767c36240d4774ebab9cffb25c05efb328981a068349256442034f5ff510abc72693398b6e320b9ab8f59ed3b9d87e8b6d9f44f30884ff62afb960c531
|
7
|
+
data.tar.gz: 80a67a76677dbad046463541b7e9d8208033243c9a0f4633d48e25e1375adc0085f261d50e3b53fdc34b2c89152652b30f13d3432ce59c5ebe0f3918bf071b4a
|
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.5.1 - 2025-04-21
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
|
10
|
+
- Struct defined with bit attributes could not be cloned.
|
11
|
+
|
12
|
+
## 0.5.0 - 2025-02-17
|
13
|
+
|
14
|
+
### Added
|
15
|
+
|
16
|
+
- Add `String#b` to mimic Ruby's `String`
|
17
|
+
- Add a lot of examples in YARD documentation. These examples are checked using yard-doctest.
|
18
|
+
|
19
|
+
### Deprecated
|
20
|
+
|
21
|
+
- Deprecate `BinStruct.force_binary` and `Struct.force_binary` in favor of Ruby's `String#b`
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- Fix `String#to_s` when static_length is set. `#to_s` was not aware of static length option.
|
26
|
+
|
6
27
|
## 0.4.0 - 2025-02-13
|
7
28
|
|
8
29
|
### Added
|
@@ -6,13 +6,10 @@
|
|
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
|
-
# To simply define a new TLV class, do:
|
12
|
+
# You have to define a concrete class from AbstractTLV
|
16
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
|
@@ -23,26 +20,33 @@ 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
|
-
#
|
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
|
35
37
|
# MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
|
36
38
|
# length_class: BinStruct::Int16,
|
37
|
-
# value_class:
|
38
|
-
# tlv = MyTLV.new(type: 1, value: '
|
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
|
+
# @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
|
46
50
|
# MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
|
47
51
|
# length_class: BinStruct::Int16,
|
48
52
|
# aliases: { code: :type })
|
data/lib/bin_struct/array.rb
CHANGED
@@ -87,6 +87,23 @@ module BinStruct
|
|
87
87
|
|
88
88
|
# @param [Hash] options
|
89
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
|
90
107
|
def initialize(options = {})
|
91
108
|
@counter = options[:counter]
|
92
109
|
@array = []
|
@@ -271,30 +288,65 @@ module BinStruct
|
|
271
288
|
end
|
272
289
|
|
273
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]
|
274
298
|
class ArrayOfInt8 < Array
|
275
299
|
include ArrayOfIntMixin
|
276
300
|
set_of Int8
|
277
301
|
end
|
278
302
|
|
279
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]
|
280
311
|
class ArrayOfInt16 < Array
|
281
312
|
include ArrayOfIntMixin
|
282
313
|
set_of Int16
|
283
314
|
end
|
284
315
|
|
285
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]
|
286
324
|
class ArrayOfInt16le < Array
|
287
325
|
include ArrayOfIntMixin
|
288
326
|
set_of Int16le
|
289
327
|
end
|
290
328
|
|
291
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]
|
292
337
|
class ArrayOfInt32 < BinStruct::Array
|
293
338
|
include ArrayOfIntMixin
|
294
339
|
set_of Int32
|
295
340
|
end
|
296
341
|
|
297
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]
|
298
350
|
class ArrayOfInt32le < BinStruct::Array
|
299
351
|
include ArrayOfIntMixin
|
300
352
|
set_of Int32le
|
data/lib/bin_struct/bit_attr.rb
CHANGED
@@ -7,13 +7,23 @@
|
|
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
|
18
28
|
# @abstract Subclasses must de derived using {.create}.
|
19
29
|
# @author LemonTree55
|
@@ -22,8 +32,6 @@ module BinStruct
|
|
22
32
|
|
23
33
|
# @return [Integer] width in bits of bit attribute
|
24
34
|
attr_reader :width
|
25
|
-
# @return [::Array[Symbol]]
|
26
|
-
attr_reader :bit_methods
|
27
35
|
|
28
36
|
# @private
|
29
37
|
Parameters = Struct.new(:width, :fields, :int)
|
@@ -35,6 +43,10 @@ module BinStruct
|
|
35
43
|
# @return [Parameters]
|
36
44
|
attr_reader :parameters
|
37
45
|
|
46
|
+
# @private
|
47
|
+
# @return [::Array[Symbol]]
|
48
|
+
attr_reader :bit_methods
|
49
|
+
|
38
50
|
# Create a new {BitAttr} subclass with specified parameters
|
39
51
|
# @param [Integer] width size of bitfields in bits. Must be a size of an {Int} (8, 16, 24, 32 or 64 bits).
|
40
52
|
# @param [:big,:little,:native] endian endianess of bit attribute as an integer
|
@@ -54,10 +66,7 @@ module BinStruct
|
|
54
66
|
total_size = fields.reduce(0) { |acc, ary| acc + ary.last }
|
55
67
|
raise ArgumentError, "sum of bitfield sizes is not equal to #{width}" unless total_size == width
|
56
68
|
|
57
|
-
cache[hsh] =
|
58
|
-
int_klass = BinStruct.const_get("Int#{width}")
|
59
|
-
@parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
|
60
|
-
end
|
69
|
+
cache[hsh] = create_subclass(width, endian, fields.dup.freeze)
|
61
70
|
end
|
62
71
|
|
63
72
|
private
|
@@ -74,6 +83,39 @@ module BinStruct
|
|
74
83
|
def compute_hash(*params)
|
75
84
|
Digest::MD5.digest(Marshal.dump(params))
|
76
85
|
end
|
86
|
+
|
87
|
+
def create_subclass(width, endian, fields)
|
88
|
+
klass = Class.new(self) do
|
89
|
+
int_klass = BinStruct.const_get("Int#{width}")
|
90
|
+
@parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
|
91
|
+
@bit_methods = []
|
92
|
+
end
|
93
|
+
|
94
|
+
define_methods(klass, fields)
|
95
|
+
klass
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Class] {BitAttr} subclass
|
99
|
+
# @param [Hash{Symbol => Integer}] fields
|
100
|
+
# @return [void]
|
101
|
+
def define_methods(klass, fields)
|
102
|
+
define_str = +''
|
103
|
+
fields.each do |name, size|
|
104
|
+
define_str << "def #{name}; @data[#{name.inspect}]; end\n"
|
105
|
+
klass.bit_methods << name
|
106
|
+
klass.bit_methods << :"#{name}="
|
107
|
+
|
108
|
+
if size == 1
|
109
|
+
define_str << "def #{name}?; @data[#{name.inspect}] != 0; end\n"
|
110
|
+
klass.bit_methods << :"#{name}?"
|
111
|
+
define_str << "def #{name}=(val); v = case val when TrueClass; 1 when FalseClass; 0 else val end; " \
|
112
|
+
"@data[#{name.inspect}] = v; end\n"
|
113
|
+
else
|
114
|
+
define_str << "def #{name}=(val); @data[#{name.inspect}] = val; end\n"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
klass.class_eval(define_str)
|
118
|
+
end
|
77
119
|
end
|
78
120
|
|
79
121
|
# Initialize bit attribute
|
@@ -91,13 +133,21 @@ module BinStruct
|
|
91
133
|
@data = {}
|
92
134
|
@bit_methods = []
|
93
135
|
|
94
|
-
parameters.fields.
|
136
|
+
parameters.fields.each_key do |name|
|
95
137
|
@data[name] = opts[name] || 0
|
96
|
-
define_methods(name, size)
|
97
138
|
end
|
98
139
|
@bit_methods.freeze
|
99
140
|
end
|
100
141
|
|
142
|
+
def initialize_copy(_other)
|
143
|
+
@data = @data.dup
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [::Array[Symbol]]
|
147
|
+
def bit_methods
|
148
|
+
self.class.bit_methods
|
149
|
+
end
|
150
|
+
|
101
151
|
# Get type name
|
102
152
|
# @return [::String]
|
103
153
|
def type_name
|
@@ -165,24 +215,5 @@ module BinStruct
|
|
165
215
|
|
166
216
|
self
|
167
217
|
end
|
168
|
-
|
169
|
-
# @param [Symbol] name
|
170
|
-
# @return [void]
|
171
|
-
def define_methods(name, size)
|
172
|
-
instance_eval "def #{name}; @data[#{name.inspect}]; end\n", __FILE__, __LINE__ # def name; data[:name]; end
|
173
|
-
bit_methods << name
|
174
|
-
bit_methods << :"#{name}="
|
175
|
-
|
176
|
-
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
177
|
-
if size == 1
|
178
|
-
instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
|
179
|
-
instance_eval "def #{name}=(val); v = case val when TrueClass; 1 when FalseClass; 0 else val end; " \
|
180
|
-
"@data[#{name.inspect}] = v; end", __FILE__, __LINE__ - 1
|
181
|
-
bit_methods << :"#{name}?"
|
182
|
-
else
|
183
|
-
instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
|
184
|
-
end
|
185
|
-
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
186
|
-
end
|
187
218
|
end
|
188
219
|
end
|
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
|
@@ -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,21 +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
|
-
# == Simple example
|
16
|
-
# enum = Int8Enum.new('low' => 0, 'medium' => 1, 'high' => 2})
|
17
|
-
# In this example, +enum+ is a 8-bit attribute which may take one
|
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
15
|
# Setting an unknown name will raise an exception:
|
25
16
|
# enum.value = 'unknown' # => raise!
|
26
17
|
# But {#read} and {#value=} will not raise when reading/setting an out-of-bound integer. This
|
27
18
|
# to enable decoding (or forging) of bad packets.
|
28
19
|
# enum.read("\x05".b).value # => 5
|
29
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
|
+
#
|
30
32
|
# @author Sylvain Daubert (2016-2024)
|
31
33
|
# @author LemonTree55
|
32
34
|
class Enum < Int
|
@@ -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]
|
@@ -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
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:
|
@@ -268,13 +268,15 @@ module BinStruct
|
|
268
268
|
bit_attr_klass = BitAttr.create(width: width, endian: endian, **fields)
|
269
269
|
define_attr(attr, bit_attr_klass, default: default)
|
270
270
|
fields.each_key { |field| register_bit_attr_field(attr, field) }
|
271
|
+
define_str = +''
|
271
272
|
bit_attr_klass.new.bit_methods.each do |meth|
|
272
|
-
if meth.to_s.end_with?('=')
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
273
|
+
define_str << if meth.to_s.end_with?('=')
|
274
|
+
"def #{meth}(value); self[:#{attr}].#{meth}(value); end\n"
|
275
|
+
else
|
276
|
+
"def #{meth}; self[:#{attr}].#{meth}; end\n"
|
277
|
+
end
|
277
278
|
end
|
279
|
+
class_eval(define_str)
|
278
280
|
end
|
279
281
|
|
280
282
|
# Define a bit attribute, before another attribute
|
@@ -443,12 +445,11 @@ module BinStruct
|
|
443
445
|
def read(str)
|
444
446
|
return self if str.nil?
|
445
447
|
|
446
|
-
force_binary(str)
|
447
448
|
start = 0
|
448
449
|
attributes.each do |attr|
|
449
450
|
next unless present?(attr)
|
450
451
|
|
451
|
-
obj = self[attr].read(str[start..])
|
452
|
+
obj = self[attr].read(str.b[start..])
|
452
453
|
start += self[attr].sz
|
453
454
|
self[attr] = obj unless obj == self[attr]
|
454
455
|
end
|
@@ -480,7 +481,7 @@ module BinStruct
|
|
480
481
|
# @return [String]
|
481
482
|
def to_s
|
482
483
|
attributes.select { |attr| present?(attr) }
|
483
|
-
.map! { |attr|
|
484
|
+
.map! { |attr| @attributes[attr].to_s.b }.join
|
484
485
|
end
|
485
486
|
|
486
487
|
# Size of object as binary string
|
@@ -531,6 +532,7 @@ module BinStruct
|
|
531
532
|
# Force str to binary encoding
|
532
533
|
# @param [String] str
|
533
534
|
# @return [String]
|
535
|
+
# @deprecated Prefer use of Ruby's {::String#b}
|
534
536
|
def force_binary(str)
|
535
537
|
BinStruct.force_binary(str)
|
536
538
|
end
|
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LemonTree55
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-21 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.
|