ffi-bitfield 0.0.11 → 0.1.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/LICENSE.txt +1 -1
- data/README.md +33 -3
- data/lib/ffi/bit_field/class_methods.rb +98 -5
- data/lib/ffi/bit_field/instance_methods.rb +31 -7
- data/lib/ffi/bit_field/version.rb +1 -3
- data/lib/ffi/bit_field.rb +0 -2
- data/lib/ffi/bit_struct.rb +0 -2
- data/lib/ffi/managed_bit_struct.rb +0 -2
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe9149d50ec1010a72da77b760d5c9f110688a2fae95fa51fd8df155ea6f85f7
|
|
4
|
+
data.tar.gz: 6f2e4d91b7386406e89512d8a3a352c5e40a39a554ca128b70fc6c4573984dd6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18912f59cc0434df26e14b211b275478c89fe2a96852728a6213ffb134e7d5a83d698d274a3258f17735c38dc7c38ef2b9dde4a62f23d6a0903b1b356be72e1d
|
|
7
|
+
data.tar.gz: c5cda72742bdf29f27f24512d7d3263c6700ffcfc19cff4a7d4e8687cd44f60a7f7ad4a03b4939f6a55bd30a606e3e93a63cc41dccbe52b1b24ecb62901ef59e
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/ffi-bitfield)
|
|
4
4
|
[](https://github.com/kojix2/ffi-bitfield/actions/workflows/ci.yml)
|
|
5
|
+
[](https://tokei.kojix2.net/github/kojix2/ffi-bitfield)
|
|
5
6
|
|
|
6
7
|
Bit field for [Ruby-FFI](https://github.com/ffi/ffi)
|
|
7
8
|
|
|
@@ -15,8 +16,8 @@ gem install ffi-bitfield
|
|
|
15
16
|
|
|
16
17
|
Classes
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
- class BitStruct < FFI::Struct
|
|
20
|
+
- class ManagedBitStruct < FFI::ManagedStruct
|
|
20
21
|
|
|
21
22
|
Loading
|
|
22
23
|
|
|
@@ -70,6 +71,35 @@ s[:y] = 1
|
|
|
70
71
|
p s[:a] # 64
|
|
71
72
|
```
|
|
72
73
|
|
|
74
|
+
### Typed Bit Fields
|
|
75
|
+
|
|
76
|
+
You can use the `bit_fields_typed` method to define bit fields with type information. This method accepts a hash where keys are field names and values are arrays containing `[width, type]`. For boolean fields (width of 1 with `:bool` type), it automatically generates a `?` helper method:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
class AccessFlags < FFI::BitStruct
|
|
80
|
+
layout \
|
|
81
|
+
:flags, :uint8
|
|
82
|
+
|
|
83
|
+
bit_fields_typed :flags,
|
|
84
|
+
revoked: [1, :bool], # Creates revoked? method
|
|
85
|
+
expired: [1, :bool], # Creates expired? method
|
|
86
|
+
some_string: [4, :string],
|
|
87
|
+
reserved: [2, :int]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
flags = AccessFlags.new
|
|
91
|
+
flags[:revoked] = 1
|
|
92
|
+
flags[:expired] = 0
|
|
93
|
+
|
|
94
|
+
p flags.revoked? # => true
|
|
95
|
+
p flags.expired? # => false
|
|
96
|
+
|
|
97
|
+
flags[:expired] = 1
|
|
98
|
+
p flags.expired? # => true
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `?` methods are only generated for fields that are 1 bit wide and have type `:bool`. Other field types can be used for documentation purposes and future functionality.
|
|
102
|
+
|
|
73
103
|
### Inspecting Bit Fields
|
|
74
104
|
|
|
75
105
|
You can use the `bit_field_members` method to get a hash of bit fields grouped by parent field:
|
|
@@ -128,7 +158,7 @@ bundle install
|
|
|
128
158
|
bundle exec rake test
|
|
129
159
|
```
|
|
130
160
|
|
|
131
|
-
|
|
161
|
+
- [ffi-bitfield - read/write bit fields with Ruby-FFI](https://dev.to/kojix2/ffi-bitfield-g4h)
|
|
132
162
|
|
|
133
163
|
## Contributing
|
|
134
164
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
module FFI
|
|
4
2
|
module BitField
|
|
5
3
|
# ClassMethods provides methods for defining bit field layouts.
|
|
@@ -147,9 +145,13 @@ module FFI
|
|
|
147
145
|
member_names << name.to_sym
|
|
148
146
|
widths << width.to_i
|
|
149
147
|
end
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
|
|
149
|
+
# Prevent redefining bit fields on the same parent field
|
|
150
|
+
ensure_parent_not_defined(parent_name)
|
|
151
|
+
|
|
152
|
+
# Validate total width against parent size
|
|
153
|
+
validate_total_width(parent_name, widths)
|
|
154
|
+
starts = bit_start_offsets(widths)
|
|
153
155
|
member_names.zip(starts, widths).each do |name, start, width|
|
|
154
156
|
@bit_field_hash_table[name] = [parent_name, start, width]
|
|
155
157
|
end
|
|
@@ -157,6 +159,97 @@ module FFI
|
|
|
157
159
|
parent_name
|
|
158
160
|
end
|
|
159
161
|
alias bit_field bit_fields
|
|
162
|
+
|
|
163
|
+
# Defines typed bit fields within a parent field using hash syntax.
|
|
164
|
+
#
|
|
165
|
+
# @param [Symbol] parent_name The name of the parent field
|
|
166
|
+
# @param [Hash] field_definitions A hash where keys are field names and values are arrays [width, type]
|
|
167
|
+
# @return [Symbol] parent_name The name of the parent field
|
|
168
|
+
#
|
|
169
|
+
# @example Define typed bit fields with automatic boolean helpers
|
|
170
|
+
# bit_fields_typed :flags,
|
|
171
|
+
# revoked: [1, :bool], # Creates revoked? methods
|
|
172
|
+
# expired: [1, :bool], # Creates expired? methods
|
|
173
|
+
# some_string: [4, :string] # Creates some_string method
|
|
174
|
+
#
|
|
175
|
+
# @note For fields with width 1 and type :bool, a "?" helper method is automatically created
|
|
176
|
+
# @note The total bit width should not exceed the size of the parent field.
|
|
177
|
+
def bit_fields_typed(parent_name, field_definitions)
|
|
178
|
+
@bit_field_hash_table = {} unless instance_variable_defined?(:@bit_field_hash_table)
|
|
179
|
+
@bit_field_type_table = {} unless instance_variable_defined?(:@bit_field_type_table)
|
|
180
|
+
|
|
181
|
+
parent_name = parent_name.to_sym
|
|
182
|
+
member_names = []
|
|
183
|
+
widths = []
|
|
184
|
+
types = []
|
|
185
|
+
|
|
186
|
+
field_definitions.each do |name, definition|
|
|
187
|
+
width, type = definition
|
|
188
|
+
member_names << name.to_sym
|
|
189
|
+
widths << width.to_i
|
|
190
|
+
types << type.to_sym
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Prevent redefining bit fields on the same parent field
|
|
194
|
+
ensure_parent_not_defined(parent_name)
|
|
195
|
+
|
|
196
|
+
# Validate total width against parent size
|
|
197
|
+
validate_total_width(parent_name, widths)
|
|
198
|
+
|
|
199
|
+
starts = bit_start_offsets(widths)
|
|
200
|
+
|
|
201
|
+
member_names.zip(starts, widths, types).each do |name, start, width, type|
|
|
202
|
+
@bit_field_hash_table[name] = [parent_name, start, width]
|
|
203
|
+
@bit_field_type_table[name] = type
|
|
204
|
+
|
|
205
|
+
# Generate "?" method for boolean fields with width 1
|
|
206
|
+
next unless width == 1 && type == :bool
|
|
207
|
+
|
|
208
|
+
define_method(:"#{name}?") do
|
|
209
|
+
self[name] == 1
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
parent_name
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
# Return cumulative start positions for given widths
|
|
219
|
+
# e.g., [3, 4, 1] => [0, 3, 7]
|
|
220
|
+
def bit_start_offsets(widths)
|
|
221
|
+
starts = []
|
|
222
|
+
sum = 0
|
|
223
|
+
widths.each do |w|
|
|
224
|
+
starts << sum
|
|
225
|
+
sum += w
|
|
226
|
+
end
|
|
227
|
+
starts
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Raise if the same parent already has bit fields
|
|
231
|
+
def ensure_parent_not_defined(parent_name)
|
|
232
|
+
return unless @bit_field_hash_table&.any? { |_n, info| info[0] == parent_name }
|
|
233
|
+
|
|
234
|
+
raise ArgumentError, "bit_fields for :#{parent_name} already defined"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Return parent field size in bits (nil if unknown)
|
|
238
|
+
def parent_size_bits(parent_name)
|
|
239
|
+
field = layout[parent_name] # nil if not found
|
|
240
|
+
return nil unless field&.respond_to?(:type)
|
|
241
|
+
|
|
242
|
+
field.type.size * 8
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Raise if total width exceeds parent size
|
|
246
|
+
def validate_total_width(parent_name, widths)
|
|
247
|
+
size = parent_size_bits(parent_name)
|
|
248
|
+
return unless size
|
|
249
|
+
|
|
250
|
+
total = widths.sum
|
|
251
|
+
raise ArgumentError, "Bit width #{total} exceeds :#{parent_name} size (#{size} bits)" if total > size
|
|
252
|
+
end
|
|
160
253
|
end
|
|
161
254
|
end
|
|
162
255
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
module FFI
|
|
4
2
|
module BitField
|
|
5
3
|
# InstanceMethods provides methods for reading and writing bit field values.
|
|
@@ -42,7 +40,7 @@ module FFI
|
|
|
42
40
|
parent_name, start, width = member_value_info(member_name)
|
|
43
41
|
if parent_name
|
|
44
42
|
value = get_member_value(parent_name)
|
|
45
|
-
(value >> start) & (
|
|
43
|
+
(value >> start) & max_value_for_width(width)
|
|
46
44
|
else
|
|
47
45
|
get_member_value(member_name)
|
|
48
46
|
end
|
|
@@ -76,7 +74,7 @@ module FFI
|
|
|
76
74
|
parent_name, start, width = field_info
|
|
77
75
|
|
|
78
76
|
# Calculate max value for this bit width
|
|
79
|
-
max_value = (
|
|
77
|
+
max_value = max_value_for_width(width)
|
|
80
78
|
|
|
81
79
|
# Handle negative values by bit-flipping
|
|
82
80
|
if value.negative?
|
|
@@ -105,20 +103,46 @@ module FFI
|
|
|
105
103
|
|
|
106
104
|
# Update the parent field with the new bit field value
|
|
107
105
|
parent_value = get_member_value(parent_name)
|
|
108
|
-
mask = (
|
|
109
|
-
new_value = (parent_value & ~mask) | ((value & (
|
|
106
|
+
mask = create_bitmask(width, start)
|
|
107
|
+
new_value = (parent_value & ~mask) | ((value & max_value_for_width(width)) << start)
|
|
110
108
|
|
|
111
109
|
set_member_value(parent_name, new_value)
|
|
112
110
|
end
|
|
113
111
|
|
|
114
112
|
private
|
|
115
113
|
|
|
114
|
+
# Calculates the maximum value that can be stored in the given bit width.
|
|
115
|
+
# For example: 3 bits can store 0b000 to 0b111, so max is 7
|
|
116
|
+
#
|
|
117
|
+
# @param [Integer] width The number of bits
|
|
118
|
+
# @return [Integer] The maximum value
|
|
119
|
+
# @example
|
|
120
|
+
# max_value_for_width(3) # => 7 (0b111)
|
|
121
|
+
# max_value_for_width(4) # => 15 (0b1111)
|
|
122
|
+
def max_value_for_width(width)
|
|
123
|
+
(1 << width) - 1
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Creates a bitmask for extracting or setting bits at a specific position.
|
|
127
|
+
# For example: width=3, start=2 creates 0b11100 (mask for bits 2,3,4)
|
|
128
|
+
#
|
|
129
|
+
# @param [Integer] width The number of bits in the mask
|
|
130
|
+
# @param [Integer] start The starting bit position (0-indexed from right)
|
|
131
|
+
# @return [Integer] The bitmask
|
|
132
|
+
# @example
|
|
133
|
+
# create_bitmask(3, 2) # => 28 (0b11100)
|
|
134
|
+
# create_bitmask(2, 0) # => 3 (0b11)
|
|
135
|
+
def create_bitmask(width, start)
|
|
136
|
+
max_value_for_width(width) << start
|
|
137
|
+
end
|
|
138
|
+
|
|
116
139
|
# Gets information about a bit field member.
|
|
117
140
|
#
|
|
118
141
|
# @param [Symbol] member_name The name of the bit field
|
|
119
142
|
# @return [Array, nil] An array containing [parent_name, start_bit, width] or nil if not a bit field
|
|
120
143
|
def member_value_info(member_name)
|
|
121
|
-
self.class.instance_variable_get(:@bit_field_hash_table)
|
|
144
|
+
hash_table = self.class.instance_variable_get(:@bit_field_hash_table)
|
|
145
|
+
hash_table&.[](member_name)
|
|
122
146
|
end
|
|
123
147
|
end
|
|
124
148
|
end
|
data/lib/ffi/bit_field.rb
CHANGED
data/lib/ffi/bit_struct.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ffi-bitfield
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kojix2
|
|
@@ -23,7 +23,7 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
-
description: bit
|
|
26
|
+
description: Provides bit field support for FFI::Struct and FFI::ManagedStruct
|
|
27
27
|
email:
|
|
28
28
|
- 2xijok@gmail.com
|
|
29
29
|
executables: []
|
|
@@ -56,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
56
56
|
- !ruby/object:Gem::Version
|
|
57
57
|
version: '0'
|
|
58
58
|
requirements: []
|
|
59
|
-
rubygems_version: 3.
|
|
59
|
+
rubygems_version: 3.6.9
|
|
60
60
|
specification_version: 4
|
|
61
|
-
summary:
|
|
61
|
+
summary: Bit fields for Ruby-FFI
|
|
62
62
|
test_files: []
|