bin_struct 0.2.1 → 0.4.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 +22 -0
- data/README.md +83 -0
- data/lib/bin_struct/abstract_tlv.rb +63 -9
- data/lib/bin_struct/array.rb +10 -5
- data/lib/bin_struct/bit_attr.rb +188 -0
- data/lib/bin_struct/cstring.rb +2 -2
- data/lib/bin_struct/enum.rb +11 -10
- data/lib/bin_struct/int.rb +1 -1
- data/lib/bin_struct/int_string.rb +9 -2
- data/lib/bin_struct/string.rb +3 -3
- data/lib/bin_struct/struct.rb +123 -135
- data/lib/bin_struct/structable.rb +1 -1
- data/lib/bin_struct/version.rb +1 -1
- data/lib/bin_struct.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aeb290e624ce4004c20cfaf40fc60ae43938c80eab8fd1df1e8aa908a75e26cf
|
4
|
+
data.tar.gz: 8cb5dd0abdce7421b8269a4d35c14886f57cc6440a50c924b0937e5b30fe20ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecf6acc2f5c406556598212d22ad6257d22ed646c1128a145cff1730dc537bb7acfd40903f211aad28b11fa30c11324a98b98ecebb70ad7b84ea991bcf181f25
|
7
|
+
data.tar.gz: 1dcfbb20a54f7c08d2794e0e8391b70c633d65f6ccbc41c372e46c77da6fb036fbfa9d1548462030b86603e3e41b132019da70b3c2d603f7f21116bdc842ec14
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,28 @@
|
|
3
3
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
4
4
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
5
5
|
|
6
|
+
## 0.4.0 - 2025-02-13
|
7
|
+
|
8
|
+
### Added
|
9
|
+
|
10
|
+
- Add `Struct#attribute?` to check existence of an attribute.
|
11
|
+
- Add `AbstractTLV.derive` to derive a new subclass from a concrete TLV class.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- Update and fix Yard documentation.
|
16
|
+
|
17
|
+
## 0.3.0 - 2024-12-02
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- `BitAddr` class is added. This class is used as a `Structable` type to handle bitfield attributes.
|
22
|
+
- Add `Struct.define_bit_attr`, `.define_bit_attr_before` and `.define_bit_attr_before` to define bitfield attributes.
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- `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`.
|
27
|
+
|
6
28
|
## 0.2.1 - 2024-11-25
|
7
29
|
|
8
30
|
### Added
|
data/README.md
CHANGED
@@ -19,6 +19,89 @@ Or add it to a Gemfile:
|
|
19
19
|
gem 'bin_struct'
|
20
20
|
```
|
21
21
|
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Create a struct
|
25
|
+
|
26
|
+
To create a BinStruct, create a new class inheriting from `BinStruct::Struct`. Then, defines struct attributes using `.define_attr`. `.define_bit_attr` may also be used to define bit field attributes.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'bin_struct'
|
30
|
+
|
31
|
+
class IPHeader < BinStruct::Struct
|
32
|
+
# Define a bir field, defaulting to 0x45, and splitted in 2 sub-fields: version and ihl,
|
33
|
+
# 4-bit size each
|
34
|
+
define_bit_attr :u8, default: 0x45, version: 4, ihl: 4
|
35
|
+
# Define a 8-bit unsigned integer named tos
|
36
|
+
# 1st argument: a symbol to define attribute name
|
37
|
+
# 2nd argument: a class to define attribute type. May be a type provided by BinStruct,
|
38
|
+
# or a user-defined class inheriting from one of these classes
|
39
|
+
# others arguments: options. Here, :default defines a default value for the attribute.
|
40
|
+
define_attr :tos, BinStruct::Int8, default: 0
|
41
|
+
# Define a 16-bit unsigned integer named length. Default to 20.
|
42
|
+
define_attr :length, BinStruct::Int16, default: 20
|
43
|
+
# Define a 16-bir unsigned integer named id. It is initialized with a random number
|
44
|
+
define_attr :id, BinStruct::Int16, default: ->(_) { rand(65_535) }
|
45
|
+
# Define a bit field composed of 4 subfields of 1, 1, 1 and 13 bit, respectively
|
46
|
+
define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13
|
47
|
+
# Define TTL field, a 8-bit unsigned integer, default to 64
|
48
|
+
define_attr :ttl, BinStruct::Int8, default: 64
|
49
|
+
# Define protocol field (8-bit unsigned integer)
|
50
|
+
define_attr :protocol, BinStruct::Int8
|
51
|
+
# Define checksum field (16-bit unsigned integer), default to 0
|
52
|
+
define_attr :checksum, BinStruct::Int16, default: 0
|
53
|
+
# Source and destination addresses, defined as array of 4 8-bit unsigned integers
|
54
|
+
define_attr :src, BinStruct::ArrayOfInt8, length_from: -> { 4 }
|
55
|
+
define_attr :dst, BinStruct::ArrayOfInt8, length_from: -> { 4 }
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
### Parse a binary string
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# Initialize struct from a binary string
|
63
|
+
ip = IPHeader.new.read("\x45\x00\x00\x14\x43\x21\x00\x00\x40\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01".b)
|
64
|
+
|
65
|
+
# Access some fields
|
66
|
+
p ip.version #=> 4
|
67
|
+
p ip.ihl #=> 5
|
68
|
+
p ip.id.to_s(16) #=> "4321"
|
69
|
+
p ip.protocol #=> 1
|
70
|
+
p ip.src.map { |byte| byte.to_i }.join('.') #=> "127.0.0.1"
|
71
|
+
```
|
72
|
+
|
73
|
+
```text
|
74
|
+
> p IPHeader.new.read("\x45\x00\x00\x14\x43\x21\x00\x00\x40\x01\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01")
|
75
|
+
-- IPHeader -----------------------------------------------------------
|
76
|
+
BitAttr8 u8: 69 (0x45)
|
77
|
+
version:4 ihl:5
|
78
|
+
Int8 tos: 0 (0x00)
|
79
|
+
Int16 length: 20 (0x0014)
|
80
|
+
Int16 id: 17185 (0x4321)
|
81
|
+
BitAttr16 frag: 0 (0x0000)
|
82
|
+
flag_rsv:0 flag_df:0 flag_mf:0 fragment_offset:0
|
83
|
+
Int8 ttl: 64 (0x40)
|
84
|
+
Int8 protocol: 1 (0x01)
|
85
|
+
Int16 checksum: 0 (0x0000)
|
86
|
+
ArrayOfInt8 src: 127,0,0,1
|
87
|
+
ArrayOfInt8 dst: 127,0,0,1
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
### Generate a binary string
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# Create a new struct with some fields initialized
|
95
|
+
ip = IPHeader.new(tos: 42, id: 0x1234)
|
96
|
+
|
97
|
+
# Initialize fields after creation
|
98
|
+
ip.src = [192, 168, 1, 1]
|
99
|
+
ip.dst = [192, 168, 1, 2]
|
100
|
+
|
101
|
+
# Generate binary string
|
102
|
+
ip.to_s
|
103
|
+
```
|
104
|
+
|
22
105
|
## License
|
23
106
|
|
24
107
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -13,7 +13,7 @@ module BinStruct
|
|
13
13
|
#
|
14
14
|
# ===Usage
|
15
15
|
# To simply define a new TLV class, do:
|
16
|
-
# MyTLV =
|
16
|
+
# MyTLV = BinStruct::AbstractTLV.create
|
17
17
|
# MyTLV.define_type_enum 'one' => 1, 'two' => 2
|
18
18
|
# This will define a new +MyTLV+ class, subclass of {AbstractTLV}. This class will
|
19
19
|
# define 3 attributes:
|
@@ -32,9 +32,9 @@ module BinStruct
|
|
32
32
|
#
|
33
33
|
# ===Advanced usage
|
34
34
|
# Each attribute's type may be changed at generating TLV class:
|
35
|
-
# MyTLV =
|
36
|
-
#
|
37
|
-
#
|
35
|
+
# MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
|
36
|
+
# length_class: BinStruct::Int16,
|
37
|
+
# value_class: PacketGen::Header::IP::Addr)
|
38
38
|
# tlv = MyTLV.new(type: 1, value: '1.2.3.4')
|
39
39
|
# tlv.type #=> 1
|
40
40
|
# tlv.length #=> 4
|
@@ -43,9 +43,9 @@ module BinStruct
|
|
43
43
|
#
|
44
44
|
# Some aliases may also be defined. For example, to create a TLV type
|
45
45
|
# whose +type+ attribute should be named +code+:
|
46
|
-
# MyTLV =
|
47
|
-
#
|
48
|
-
#
|
46
|
+
# MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
|
47
|
+
# length_class: BinStruct::Int16,
|
48
|
+
# aliases: { code: :type })
|
49
49
|
# tlv = MyTLV.new(code: 1, value: 'abcd')
|
50
50
|
# tlv.code #=> 1
|
51
51
|
# tlv.type #=> 1
|
@@ -77,11 +77,12 @@ module BinStruct
|
|
77
77
|
# in the desired order.
|
78
78
|
# @param [::String] attr_in_length give attributes to compute length on.
|
79
79
|
# @return [Class]
|
80
|
+
# @raise [Error] Called on {AbstractTLV} subclass
|
80
81
|
def create(type_class: Int8Enum, length_class: Int8, value_class: String,
|
81
82
|
aliases: {}, attr_order: 'TLV', attr_in_length: 'V')
|
82
83
|
unless equal?(AbstractTLV)
|
83
84
|
raise Error,
|
84
|
-
'.create cannot be called on a subclass of
|
85
|
+
'.create cannot be called on a subclass of BinStruct::AbstractTLV'
|
85
86
|
end
|
86
87
|
|
87
88
|
klass = Class.new(self)
|
@@ -91,7 +92,7 @@ module BinStruct
|
|
91
92
|
check_attr_in_length(attr_in_length)
|
92
93
|
check_attr_order(attr_order)
|
93
94
|
generate_attributes(klass, attr_order, type_class, length_class, value_class)
|
94
|
-
|
95
|
+
generate_aliases_for(klass, aliases)
|
95
96
|
aliases.each do |al, orig|
|
96
97
|
klass.instance_eval do
|
97
98
|
alias_method al, orig if klass.method_defined?(orig)
|
@@ -103,6 +104,50 @@ module BinStruct
|
|
103
104
|
end
|
104
105
|
# rubocop:enable Metrics/ParameterLists
|
105
106
|
|
107
|
+
# On inheritage, copy aliases and attr_in_length
|
108
|
+
# @param [Class] klass inheriting class
|
109
|
+
# @return [void]
|
110
|
+
# @since 0.4.0
|
111
|
+
# @author LemonTree55
|
112
|
+
def inherited(klass)
|
113
|
+
super
|
114
|
+
|
115
|
+
aliases = @aliases.clone
|
116
|
+
attr_in_length = @attr_in_length.clone
|
117
|
+
|
118
|
+
klass.class_eval do
|
119
|
+
@aliases = aliases
|
120
|
+
@attr_in_length = attr_in_length
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Derive a new TLV class from an existing one
|
125
|
+
# @param [Class,nil] type_class New class to use for +type+. Unchanged if +nil+.
|
126
|
+
# @param [Class,nil] length_class New class to use for +length+. Unchanged if +nil+.
|
127
|
+
# @param [Class,nil] value_class New class to use for +value+. Unchanged if +nil+.
|
128
|
+
# @return [Class]
|
129
|
+
# @raise [Error] Called on {AbstractTLV} class
|
130
|
+
# @since 0.4.0
|
131
|
+
# @author LemonTree55
|
132
|
+
# @example
|
133
|
+
# # TLV with type and length on 16 bits, value is a BinStruct::String
|
134
|
+
# FirstTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16, length_class: BinStruct::Int16)
|
135
|
+
# # TLV with same type and length classes than FirstTLV, but value is an array of Int8
|
136
|
+
# SecondTLV = FirstTLV.derive(value_class: BinStruct::ArrayOfInt8)
|
137
|
+
def derive(type_class: nil, length_class: nil, value_class: nil, aliases: {})
|
138
|
+
raise Error, ".derive cannot be called on #{name}" if equal?(AbstractTLV)
|
139
|
+
|
140
|
+
klass = Class.new(self)
|
141
|
+
klass.aliases.merge!(aliases)
|
142
|
+
generate_aliases_for(klass, aliases)
|
143
|
+
|
144
|
+
klass.attr_defs[:type].type = type_class unless type_class.nil?
|
145
|
+
klass.attr_defs[:length].type = length_class unless length_class.nil?
|
146
|
+
klass.attr_defs[:value].type = value_class unless value_class.nil?
|
147
|
+
|
148
|
+
klass
|
149
|
+
end
|
150
|
+
|
106
151
|
# @!attribute type
|
107
152
|
# @abstract
|
108
153
|
# Type attribute for real TLV class
|
@@ -168,6 +213,15 @@ module BinStruct
|
|
168
213
|
end
|
169
214
|
end
|
170
215
|
end
|
216
|
+
|
217
|
+
def generate_aliases_for(klass, aliases)
|
218
|
+
aliases.each do |al, orig|
|
219
|
+
klass.instance_eval do
|
220
|
+
alias_method al, orig if klass.method_defined?(orig)
|
221
|
+
alias_method :"#{al}=", :"#{orig}=" if klass.method_defined?(:"#{orig}=")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
171
225
|
end
|
172
226
|
|
173
227
|
# @!attribute type
|
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]
|
@@ -112,6 +113,7 @@ module BinStruct
|
|
112
113
|
|
113
114
|
# Clear array. Reset associated counter, if any.
|
114
115
|
# @return [void]
|
116
|
+
# @see #clear
|
115
117
|
def clear!
|
116
118
|
@array.clear
|
117
119
|
@counter&.from_human(0)
|
@@ -137,7 +139,8 @@ module BinStruct
|
|
137
139
|
|
138
140
|
# @abstract depend on private method +#record_from_hash+ which should be
|
139
141
|
# declared by subclasses.
|
140
|
-
# Add an object to this array. Do not update associated counter.
|
142
|
+
# Add an object to this array. Do not update associated counter. If associated must be incremented, use
|
143
|
+
# {#<<}
|
141
144
|
# @param [Object] obj type depends on subclass
|
142
145
|
# @return [self]
|
143
146
|
# @see #<<
|
@@ -154,9 +157,11 @@ module BinStruct
|
|
154
157
|
|
155
158
|
# @abstract depend on private method +#record_from_hash+ which should be
|
156
159
|
# declared by subclasses.
|
157
|
-
# Add an object to this array, and increment associated counter, if any
|
160
|
+
# Add an object to this array, and increment associated counter, if any. If associated counter must not be
|
161
|
+
# incremented, use {#push}.
|
158
162
|
# @param [Object] obj type depends on subclass
|
159
163
|
# @return [self]
|
164
|
+
# @see #push
|
160
165
|
def <<(obj)
|
161
166
|
push(obj)
|
162
167
|
@counter&.from_human(@counter.to_i + 1)
|
@@ -0,0 +1,188 @@
|
|
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
|
+
# @abstract Subclasses must de derived using {.create}.
|
19
|
+
# @author LemonTree55
|
20
|
+
class BitAttr
|
21
|
+
include Structable
|
22
|
+
|
23
|
+
# @return [Integer] width in bits of bit attribute
|
24
|
+
attr_reader :width
|
25
|
+
# @return [::Array[Symbol]]
|
26
|
+
attr_reader :bit_methods
|
27
|
+
|
28
|
+
# @private
|
29
|
+
Parameters = Struct.new(:width, :fields, :int)
|
30
|
+
|
31
|
+
class << self
|
32
|
+
@cache = {}
|
33
|
+
|
34
|
+
# @private
|
35
|
+
# @return [Parameters]
|
36
|
+
attr_reader :parameters
|
37
|
+
|
38
|
+
# Create a new {BitAttr} subclass with specified parameters
|
39
|
+
# @param [Integer] width size of bitfields in bits. Must be a size of an {Int} (8, 16, 24, 32 or 64 bits).
|
40
|
+
# @param [:big,:little,:native] endian endianess of bit attribute as an integer
|
41
|
+
# @param [Hash{Symbol=>Integer}] fields hash associating field names with their size. Total size MUST be equal
|
42
|
+
# to +width+.
|
43
|
+
# @return [Class]
|
44
|
+
# @raise [ArgumentError] raise if:
|
45
|
+
# * width is not a size of one of {Int} subclasses,
|
46
|
+
# * sum of bitfield sizes is not equal to +width+
|
47
|
+
def create(width:, endian: :big, **fields)
|
48
|
+
raise ArgumentError, 'with must be 8, 16, 24, 32 or 64' unless [8, 16, 24, 32, 64].include?(width)
|
49
|
+
|
50
|
+
hsh = compute_hash(width, endian, fields)
|
51
|
+
cached = cache[hsh]
|
52
|
+
return cached if cached
|
53
|
+
|
54
|
+
total_size = fields.reduce(0) { |acc, ary| acc + ary.last }
|
55
|
+
raise ArgumentError, "sum of bitfield sizes is not equal to #{width}" unless total_size == width
|
56
|
+
|
57
|
+
cache[hsh] = Class.new(self) do
|
58
|
+
int_klass = BinStruct.const_get("Int#{width}")
|
59
|
+
@parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @return [Hash{::String=>Class}]
|
66
|
+
def cache
|
67
|
+
return @cache if defined? @cache
|
68
|
+
|
69
|
+
@cache = {}
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [::Array] params
|
73
|
+
# @return [::String]
|
74
|
+
def compute_hash(*params)
|
75
|
+
Digest::MD5.digest(Marshal.dump(params))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Initialize bit attribute
|
80
|
+
# @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
|
81
|
+
# initialization values
|
82
|
+
# @return [self]
|
83
|
+
# @raise [NotImplementedError] raised when called on {BitAttr} class
|
84
|
+
def initialize(opts = {})
|
85
|
+
parameters = self.class.parameters
|
86
|
+
raise NotImplementedError, "#initialize may only be called on subclass of #{self.class}" if parameters.nil?
|
87
|
+
|
88
|
+
@width = parameters.width
|
89
|
+
@fields = parameters.fields
|
90
|
+
@int = parameters.int.dup
|
91
|
+
@data = {}
|
92
|
+
@bit_methods = []
|
93
|
+
|
94
|
+
parameters.fields.each do |name, size|
|
95
|
+
@data[name] = opts[name] || 0
|
96
|
+
define_methods(name, size)
|
97
|
+
end
|
98
|
+
@bit_methods.freeze
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get type name
|
102
|
+
# @return [::String]
|
103
|
+
def type_name
|
104
|
+
return @type_name if defined? @type_name
|
105
|
+
|
106
|
+
endian_suffix = case @int.endian
|
107
|
+
when :big then ''
|
108
|
+
when :little then 'le'
|
109
|
+
when :native then 'n'
|
110
|
+
end
|
111
|
+
@type_name = "BitAttr#{@width}#{endian_suffix}"
|
112
|
+
end
|
113
|
+
|
114
|
+
# Populate bit attribute from +str+
|
115
|
+
# @param [#to_s,nil] str
|
116
|
+
# @return [self]
|
117
|
+
def read(str)
|
118
|
+
return self if str.nil?
|
119
|
+
|
120
|
+
@int.read(str)
|
121
|
+
compute_data(@int.to_i)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Give integer associated to this attribute
|
125
|
+
# @return [Integer]
|
126
|
+
def to_i
|
127
|
+
v = 0
|
128
|
+
@fields.each do |name, size|
|
129
|
+
v <<= size
|
130
|
+
v |= @data[name]
|
131
|
+
end
|
132
|
+
|
133
|
+
v
|
134
|
+
end
|
135
|
+
alias to_human to_i
|
136
|
+
|
137
|
+
# Return binary string
|
138
|
+
# @return [::String]
|
139
|
+
def to_s
|
140
|
+
@int.value = to_i
|
141
|
+
@int.to_s
|
142
|
+
end
|
143
|
+
|
144
|
+
# Set fields from associated integer
|
145
|
+
# @param [#to_i] value
|
146
|
+
# @return [self]
|
147
|
+
def from_human(value)
|
148
|
+
compute_data(value.to_i)
|
149
|
+
end
|
150
|
+
|
151
|
+
def format_inspect
|
152
|
+
str = @int.format_inspect << "\n"
|
153
|
+
str << @data.map { |name, value| "#{name}:#{value}" }.join(' ')
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
# @param [Integer] value
|
159
|
+
# @return [self]
|
160
|
+
def compute_data(value)
|
161
|
+
@fields.reverse_each do |name, size|
|
162
|
+
@data[name] = value & ((2**size) - 1)
|
163
|
+
value >>= size
|
164
|
+
end
|
165
|
+
|
166
|
+
self
|
167
|
+
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
|
+
end
|
188
|
+
end
|
data/lib/bin_struct/cstring.rb
CHANGED
@@ -76,14 +76,14 @@ module BinStruct
|
|
76
76
|
|
77
77
|
# @param [Hash] options
|
78
78
|
# @option options [Integer] :static_length set a static length for this string
|
79
|
-
# @option options [::String] :value string value (default to +
|
79
|
+
# @option options [::String] :value string value (default to +""+)
|
80
80
|
def initialize(options = {})
|
81
81
|
register_internal_string(options[:value] || +'')
|
82
82
|
@static_length = options[:static_length]
|
83
83
|
end
|
84
84
|
|
85
85
|
# Populate self from binary string
|
86
|
-
# @param [
|
86
|
+
# @param [#to_s] str
|
87
87
|
# @return [self]
|
88
88
|
def read(str)
|
89
89
|
s = str.to_s
|
data/lib/bin_struct/enum.rb
CHANGED
@@ -13,19 +13,20 @@ module BinStruct
|
|
13
13
|
# and named values.
|
14
14
|
#
|
15
15
|
# == Simple example
|
16
|
-
#
|
16
|
+
# enum = Int8Enum.new('low' => 0, 'medium' => 1, 'high' => 2})
|
17
17
|
# In this example, +enum+ is a 8-bit attribute which may take one
|
18
18
|
# among three values: +low+, +medium+ or +high+:
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# Setting an unknown
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# But {#read} will not raise when reading an outbound value. This
|
19
|
+
# enum.value = 'high'
|
20
|
+
# enum.value # => 2
|
21
|
+
# enum.value = 1
|
22
|
+
# enum.value # => 1
|
23
|
+
# enum.to_human # => "medium"
|
24
|
+
# Setting an unknown name will raise an exception:
|
25
|
+
# enum.value = 'unknown' # => raise!
|
26
|
+
# But {#read} and {#value=} will not raise when reading/setting an out-of-bound integer. This
|
28
27
|
# to enable decoding (or forging) of bad packets.
|
28
|
+
# enum.read("\x05".b).value # => 5
|
29
|
+
# enum.value = 4 # => 4
|
29
30
|
# @author Sylvain Daubert (2016-2024)
|
30
31
|
# @author LemonTree55
|
31
32
|
class Enum < Int
|
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
|
@@ -61,7 +68,7 @@ module BinStruct
|
|
61
68
|
# @return [::String]
|
62
69
|
def string=(str)
|
63
70
|
@length.value = str.to_s.size
|
64
|
-
@string
|
71
|
+
@string.read(str)
|
65
72
|
end
|
66
73
|
|
67
74
|
# Get binary string
|
@@ -82,7 +89,7 @@ module BinStruct
|
|
82
89
|
# Get human readable string
|
83
90
|
# @return [::String]
|
84
91
|
def to_human
|
85
|
-
@string
|
92
|
+
@string.to_s
|
86
93
|
end
|
87
94
|
|
88
95
|
# Set length from internal string length
|
data/lib/bin_struct/string.rb
CHANGED
@@ -32,7 +32,7 @@ module BinStruct
|
|
32
32
|
# @option options [Int,Proc] :length_from object or proc from which
|
33
33
|
# takes length when reading
|
34
34
|
# @option options [Integer] :static_length set a static length for this string
|
35
|
-
# @option options [::String] :value string value (default to +
|
35
|
+
# @option options [::String] :value string value (default to +""+)
|
36
36
|
def initialize(options = {})
|
37
37
|
register_internal_string(options[:value] || +'')
|
38
38
|
initialize_length_from(options)
|
@@ -47,7 +47,7 @@ module BinStruct
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# Populate String from a binary String. Limit length using {LengthFrom} or {#static_length}, if one is set.
|
50
|
-
# @param [::String] str
|
50
|
+
# @param [::String,nil] str
|
51
51
|
# @return [self]
|
52
52
|
def read(str)
|
53
53
|
s = read_with_length_from(str)
|
@@ -87,7 +87,7 @@ module BinStruct
|
|
87
87
|
self
|
88
88
|
end
|
89
89
|
|
90
|
-
# Generate binary string
|
90
|
+
# Generate "binary" string
|
91
91
|
# @return [::String]
|
92
92
|
def to_s
|
93
93
|
@string
|
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
|
@@ -435,6 +401,15 @@ module BinStruct
|
|
435
401
|
@attributes[attr] = obj
|
436
402
|
end
|
437
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
|
+
|
438
413
|
# Get all attribute names
|
439
414
|
# @return [Array<Symbol>]
|
440
415
|
def attributes
|
@@ -599,26 +574,39 @@ module BinStruct
|
|
599
574
|
end
|
600
575
|
end
|
601
576
|
|
577
|
+
# @param [Symbol] attr
|
578
|
+
# @return [void]
|
602
579
|
def initialize_optional(attr)
|
603
580
|
optional = attr_defs[attr].optional
|
604
581
|
@optional_attributes[attr] = optional if optional
|
605
582
|
end
|
606
583
|
|
584
|
+
# @return [String]
|
607
585
|
def inspect_titleize
|
608
586
|
title = self.class.to_s
|
609
|
-
|
587
|
+
"-- #{title} #{'-' * (66 - title.length)}\n"
|
610
588
|
end
|
611
589
|
|
590
|
+
# @param [:Symbol] attr
|
591
|
+
# @param [Structable] value
|
592
|
+
# @param [Integer] level
|
593
|
+
# @return [::String]
|
612
594
|
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
595
|
str = inspect_shift_level(level)
|
619
|
-
|
596
|
+
value_lines = value.format_inspect.split("\n")
|
597
|
+
str << (FMT_ATTR % [value.type_name, attr, value_lines.shift])
|
598
|
+
return str if value_lines.empty?
|
599
|
+
|
600
|
+
shift = (FMT_ATTR % ['', '', 'START']).index('START')
|
601
|
+
value_lines.each do |l|
|
602
|
+
str << inspect_shift_level(level)
|
603
|
+
str << (' ' * shift) << l << "\n"
|
604
|
+
end
|
605
|
+
str
|
620
606
|
end
|
621
607
|
|
608
|
+
# @param [Integer] level
|
609
|
+
# @return [String]
|
622
610
|
def inspect_shift_level(level = 1)
|
623
611
|
' ' * (level + 1)
|
624
612
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LemonTree55
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'BinStruct is a binary dissector and generator. It eases manipulating
|
14
14
|
complex binary data.
|
@@ -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
|
@@ -57,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
58
|
requirements:
|
58
59
|
- - ">="
|
59
60
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
61
|
+
version: 3.0.0
|
61
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
63
|
requirements:
|
63
64
|
- - ">="
|