bin_struct 0.2.1 → 0.4.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 +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
|
- - ">="
|