ctypes 0.2.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 +7 -0
- data/.standard.yml +3 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +55 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/MAINTAINERS.md +3 -0
- data/README.md +390 -0
- data/Rakefile +10 -0
- data/SECURITY.md +57 -0
- data/ctypes.gemspec +40 -0
- data/lib/ctypes/array.rb +180 -0
- data/lib/ctypes/bitfield/builder.rb +246 -0
- data/lib/ctypes/bitfield.rb +278 -0
- data/lib/ctypes/bitmap.rb +154 -0
- data/lib/ctypes/enum/builder.rb +85 -0
- data/lib/ctypes/enum.rb +201 -0
- data/lib/ctypes/exporter.rb +50 -0
- data/lib/ctypes/helpers.rb +190 -0
- data/lib/ctypes/importers/castxml/loader.rb +150 -0
- data/lib/ctypes/importers/castxml.rb +59 -0
- data/lib/ctypes/importers.rb +7 -0
- data/lib/ctypes/int.rb +147 -0
- data/lib/ctypes/missing_bytes_error.rb +24 -0
- data/lib/ctypes/pad.rb +56 -0
- data/lib/ctypes/pretty_print_helpers.rb +31 -0
- data/lib/ctypes/string.rb +154 -0
- data/lib/ctypes/struct/builder.rb +242 -0
- data/lib/ctypes/struct.rb +529 -0
- data/lib/ctypes/terminated.rb +65 -0
- data/lib/ctypes/type.rb +195 -0
- data/lib/ctypes/union/builder.rb +220 -0
- data/lib/ctypes/union.rb +637 -0
- data/lib/ctypes/version.rb +8 -0
- data/lib/ctypes.rb +102 -0
- data/sig/ctypes.rbs +4 -0
- metadata +92 -0
data/SECURITY.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Security Policies and Procedures
|
2
|
+
|
3
|
+
This document outlines security procedures and general policies for the
|
4
|
+
`ctypes` project.
|
5
|
+
|
6
|
+
- [Disclosing a security issue](#disclosing-a-security-issue)
|
7
|
+
- [Vulnerability management](#vulnerability-management)
|
8
|
+
- [Suggesting changes](#suggesting-changes)
|
9
|
+
|
10
|
+
## Disclosing a security issue
|
11
|
+
|
12
|
+
The `ctypes` maintainers take all security issues in the project
|
13
|
+
seriously. Thank you for improving the security of `ctypes`. We
|
14
|
+
appreciate your dedication to responsible disclosure and will make every effort
|
15
|
+
to acknowledge your contributions.
|
16
|
+
|
17
|
+
`ctypes` leverages GitHub's private vulnerability reporting.
|
18
|
+
|
19
|
+
To learn more about this feature and how to submit a vulnerability report,
|
20
|
+
review [GitHub's documentation on private reporting](https://docs.github.com/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability).
|
21
|
+
|
22
|
+
Here are some helpful details to include in your report:
|
23
|
+
|
24
|
+
- a detailed description of the issue
|
25
|
+
- the steps required to reproduce the issue
|
26
|
+
- versions of the project that may be affected by the issue
|
27
|
+
- if known, any mitigations for the issue
|
28
|
+
|
29
|
+
A maintainer will acknowledge the report within three (3) business days, and
|
30
|
+
will send a more detailed response within an additional three (3) business days
|
31
|
+
indicating the next steps in handling your report.
|
32
|
+
|
33
|
+
If you've been unable to successfully draft a vulnerability report via GitHub
|
34
|
+
or have not received a response during the alloted response window, please
|
35
|
+
reach out via the [Cisco Open security contact email](mailto:oss-security@cisco.com).
|
36
|
+
|
37
|
+
After the initial reply to your report, the maintainers will endeavor to keep
|
38
|
+
you informed of the progress towards a fix and full announcement, and may ask
|
39
|
+
for additional information or guidance.
|
40
|
+
|
41
|
+
## Vulnerability management
|
42
|
+
|
43
|
+
When the maintainers receive a disclosure report, they will assign it to a
|
44
|
+
primary handler.
|
45
|
+
|
46
|
+
This person will coordinate the fix and release process, which involves the
|
47
|
+
following steps:
|
48
|
+
|
49
|
+
- confirming the issue
|
50
|
+
- determining affected versions of the project
|
51
|
+
- auditing code to find any potential similar problems
|
52
|
+
- preparing fixes for all releases under maintenance
|
53
|
+
|
54
|
+
## Suggesting changes
|
55
|
+
|
56
|
+
If you have suggestions on how this process could be improved please submit an
|
57
|
+
issue or pull request.
|
data/ctypes.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/ctypes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "ctypes"
|
7
|
+
spec.version = CTypes::VERSION
|
8
|
+
spec.authors = ["David M. Lary"]
|
9
|
+
spec.email = ["dmlary@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Manipulate common C types in Ruby"
|
12
|
+
# spec.description = "TODO: Write a longer description or delete this line."
|
13
|
+
spec.homepage = "https://github.com/cisco-open/ruby-ctypes"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.1.0"
|
16
|
+
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
18
|
+
|
19
|
+
# spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
21
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
36
|
+
spec.add_dependency("dry-struct", "~> 1.0")
|
37
|
+
|
38
|
+
# For more information and examples about making a new gem, check out our
|
39
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
40
|
+
end
|
data/lib/ctypes/array.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
5
|
+
# SPDX-License-Identifier: MIT
|
6
|
+
|
7
|
+
module CTypes
|
8
|
+
# @example array of unsigned 32-bit integers
|
9
|
+
# t = CTypes::Array.new(type: CTypes::Helpers.uint32)
|
10
|
+
# t.unpack("\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb")
|
11
|
+
# # => [0xaaaaaaaa, 0xbbbbbbbb]
|
12
|
+
# t.pack([0,0xffffffff]) # => "\0\0\0\0\xff\xff\xff\xff"
|
13
|
+
#
|
14
|
+
# @example array of signed 8-bit integers
|
15
|
+
# t = CTypes::Array.new(type: CTypes::Helpers.int8)
|
16
|
+
# t.unpack("\x01\x02\x03\x04") # => [1, 2, 3, 4]
|
17
|
+
# t.pack([1, 2, 3, 4]) # => "\x01\x02\x03\x04"
|
18
|
+
#
|
19
|
+
# @example fixed-size array of 8-bit integers
|
20
|
+
# t = CTypes::Array.new(type: CTypes::Helpers.int8, size: 2)
|
21
|
+
# t.unpack("\x01\x02\x03\x04") # => [1, 2]
|
22
|
+
# t.pack([1, 2]) # => "\x01\x02"
|
23
|
+
#
|
24
|
+
# @example terminated array of 8-bit integers
|
25
|
+
# t = CTypes::Array.new(type: CTypes::Helpers.int8, terminator: -1)
|
26
|
+
# t.unpack("\x01\xff\x03\x04") # => [1]
|
27
|
+
# t.pack([1]) # => "\x01\xff"
|
28
|
+
#
|
29
|
+
# @example array of structures
|
30
|
+
# include CTypes::Helpers
|
31
|
+
# s = struct do
|
32
|
+
# attribute :type, uint8
|
33
|
+
# attribute :value, uint8
|
34
|
+
# end
|
35
|
+
# t = array(s)
|
36
|
+
# t.unpack("\x01\x02\x03\x04") # => [ { .type = 1, value = 2 },
|
37
|
+
# # { .type = 3, value = 4 } ]
|
38
|
+
# t.pack([{type: 1, value: 2}, {type: 3, value: 4}])
|
39
|
+
# # => "\x01\x02\x03\x04"
|
40
|
+
class Array
|
41
|
+
include Type
|
42
|
+
|
43
|
+
# TODO Add support for a pre-unpack terminator that checks against the
|
44
|
+
# remaining buffer before calling unpack on inner type. This allows easier
|
45
|
+
# support for DWARF-type types (.debug_line file_names)
|
46
|
+
|
47
|
+
# declare a new Array type
|
48
|
+
# @param type [CTypes::Type] type contained within the array
|
49
|
+
# @param size [Integer] number of elements in the array; nil means greedy
|
50
|
+
# unpack
|
51
|
+
# @param terminator array value that denotes the end of the array; the
|
52
|
+
# value will not be appended in `unpack` results, but will be appended
|
53
|
+
# during `pack`
|
54
|
+
def initialize(type:, size: nil, terminator: nil)
|
55
|
+
raise Error, "cannot use terminator with fixed size array" if
|
56
|
+
size && terminator
|
57
|
+
raise Error, "cannot make an Array of variable-length Unions" if
|
58
|
+
type.is_a?(Class) && type < Union && !type.fixed_size?
|
59
|
+
|
60
|
+
@type = type
|
61
|
+
@size = size
|
62
|
+
if terminator
|
63
|
+
@terminator = terminator
|
64
|
+
@term_packed = @type.pack(terminator)
|
65
|
+
@term_unpacked = @type.unpack(@term_packed)
|
66
|
+
end
|
67
|
+
|
68
|
+
@dry_type = Dry::Types["coercible.array"].of(type.dry_type)
|
69
|
+
@dry_type = if size
|
70
|
+
@dry_type.constrained(size:)
|
71
|
+
.default { ::Array.new(size, type.dry_type[]) }
|
72
|
+
else
|
73
|
+
@dry_type.default([].freeze)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
attr_reader :type, :terminator
|
77
|
+
|
78
|
+
# pack a ruby array into a binary string
|
79
|
+
# @param value [::Array] array value to pack
|
80
|
+
# @param endian [Symbol] optional endian override
|
81
|
+
# @param validate [Boolean] set to false to disable value validation
|
82
|
+
# @return [::String] binary string
|
83
|
+
def pack(value, endian: default_endian, validate: true)
|
84
|
+
value = @dry_type[value] if validate
|
85
|
+
out = value.inject(::String.new) do |o, v|
|
86
|
+
o << @type.pack(v, endian: @type.endian || endian)
|
87
|
+
end
|
88
|
+
out << @term_packed if @term_packed
|
89
|
+
out
|
90
|
+
rescue Dry::Types::ConstraintError
|
91
|
+
raise unless @size && @size > value.size
|
92
|
+
|
93
|
+
# value is short some elements; fill them in and retry
|
94
|
+
value += ::Array.new(@size - value.size, @type.default_value)
|
95
|
+
retry
|
96
|
+
end
|
97
|
+
|
98
|
+
# unpack an instance of an array from the beginning of the supplied binary
|
99
|
+
# string
|
100
|
+
# @param buf [::String] binary string
|
101
|
+
# @param endian [Symbol] optional endian override
|
102
|
+
# @return [Array(Object, ::String)] unpacked Array, unused bytes fron buf
|
103
|
+
def unpack_one(buf, endian: default_endian)
|
104
|
+
rest = buf
|
105
|
+
if @size
|
106
|
+
value = @size.times.map do |i|
|
107
|
+
o, rest = @type.unpack_one(rest, endian: @type.endian || endian)
|
108
|
+
o or raise missing_bytes_error(input: value,
|
109
|
+
need: @size * @type.size)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
# handle variable-length array; both greedy and terminated
|
113
|
+
value = []
|
114
|
+
loop do
|
115
|
+
if rest.empty?
|
116
|
+
if @term_packed
|
117
|
+
raise TerminatorNotFoundError,
|
118
|
+
"terminator not found in: %p" % buf
|
119
|
+
end
|
120
|
+
break
|
121
|
+
end
|
122
|
+
|
123
|
+
v, rest = @type.unpack_one(rest, endian: @type.endian || endian)
|
124
|
+
break if v === @term_unpacked
|
125
|
+
value << v
|
126
|
+
end
|
127
|
+
end
|
128
|
+
[value, rest]
|
129
|
+
end
|
130
|
+
|
131
|
+
# check if this Array is greedy
|
132
|
+
def greedy?
|
133
|
+
!@size && !@terminator
|
134
|
+
end
|
135
|
+
|
136
|
+
# return the size of the array if one is defined
|
137
|
+
def size
|
138
|
+
s = @size ? @size * @type.size : 0
|
139
|
+
s += @term_packed.size if @term_packed
|
140
|
+
s
|
141
|
+
end
|
142
|
+
|
143
|
+
def pretty_print(q) # :nodoc:
|
144
|
+
q.group(1, "array(", ")") do
|
145
|
+
q.pp(@type)
|
146
|
+
if @size
|
147
|
+
q.comma_breakable
|
148
|
+
q.text(@size.to_s)
|
149
|
+
end
|
150
|
+
if @terminator
|
151
|
+
q.comma_breakable
|
152
|
+
q.text("terminator: #{@terminator}")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
alias_method :inspect, :pretty_inspect # :nodoc:
|
157
|
+
|
158
|
+
def export_type(q) # :nodoc:
|
159
|
+
q << "array("
|
160
|
+
q << @type
|
161
|
+
q << ", #{@size}" if @size
|
162
|
+
q << ", terminator: #{@terminator}" if @terminator
|
163
|
+
q << ")"
|
164
|
+
q << ".with_endian(%p)" % [@endian] if @endian
|
165
|
+
end
|
166
|
+
|
167
|
+
def type_name
|
168
|
+
@size ?
|
169
|
+
"%s[%s]" % [@type.type_name, @size] :
|
170
|
+
"%s[]" % [@type.type_name]
|
171
|
+
end
|
172
|
+
|
173
|
+
def ==(other)
|
174
|
+
return false unless other.is_a?(Array)
|
175
|
+
other.type == @type &&
|
176
|
+
other.size == size &&
|
177
|
+
other.terminator == terminator
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
module CTypes
|
7
|
+
# {Bitfield} layout builder
|
8
|
+
#
|
9
|
+
# This class is used to describe the memory layout of a {Bitfield} type.
|
10
|
+
# There are two approaches provided here for describing the layout, the first
|
11
|
+
# is a constructive interface using {Builder#unsigned}, {Builder#signed},
|
12
|
+
# {Builder#align}, and {Builder#skip}, to build the bitfield from
|
13
|
+
# right-to-left. These methods track how many bits have been used, and
|
14
|
+
# automatically determine the offset of fields as they're declared.
|
15
|
+
#
|
16
|
+
# The second interface is the programmatic interface that can be used to
|
17
|
+
# generate the {Bitfield} layout from data. This uses {Builder#field} to
|
18
|
+
# explicitly declare fields using bit size, offset, and signedness.
|
19
|
+
#
|
20
|
+
# @example using the constructive interface via {CTypes::Bitfield#layout}
|
21
|
+
# class MyBits < CTypes::Bitfield
|
22
|
+
# layout do
|
23
|
+
# # the body of this block is evaluated within a Builder instance
|
24
|
+
# unsigned :bit # single bit for a at offset 0
|
25
|
+
# unsigned :two, 2 # two bits for this field at offset 1
|
26
|
+
# signed :nibble, 4 # four bit nibble as a signed int, offset 3
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example using the programmatic interface via {CTypes::Bitfield#layout}
|
31
|
+
# class MyBits < CTypes::Bitfield
|
32
|
+
# layout do
|
33
|
+
# # the body of this block is evaluated within a Builder instance
|
34
|
+
# field :bit, size: 1, offset: 0 # single bit at offset 0
|
35
|
+
# field :two, size: 2, offset: 1 # two bits at offset 1
|
36
|
+
# field :nibble, size: 4, offset: 3 # four bits at offset 3
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example construct {CTypes::Bitfield} programmatically
|
41
|
+
# b = CTypes::Bitfield.builder # => #<CTypes::Bitfield::Builder>
|
42
|
+
# b.field(:one, bits: 1, offset: 0) # one bit at offset 0, named :one
|
43
|
+
#
|
44
|
+
# # Create additional fields from data loaded from elsewhere
|
45
|
+
# extra_fields = [
|
46
|
+
# [:two, 2, 1], # two bits for this field named :two
|
47
|
+
# [:nibble, 4, 3] # four bits for the :nibble field
|
48
|
+
# ]
|
49
|
+
# extra_fields.each do |name, bits, offset|
|
50
|
+
# b.field(name, bits:, offset:)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # build the type
|
54
|
+
# t = b.build # => #<Bitfield ...>
|
55
|
+
class Bitfield::Builder
|
56
|
+
include Helpers
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@fields = []
|
60
|
+
@schema = {}
|
61
|
+
@default = {}
|
62
|
+
@offset = 0
|
63
|
+
@layout = []
|
64
|
+
@max = 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# get the offset of the next unused bit in the bitfield
|
68
|
+
attr_reader :offset
|
69
|
+
|
70
|
+
# build a {Bitfield} instance with the layout configured in this builder
|
71
|
+
# @return [Bitfield] bitfield with the layout defined in this builder
|
72
|
+
def build
|
73
|
+
k = Class.new(Bitfield)
|
74
|
+
k.send(:apply_layout, self)
|
75
|
+
k
|
76
|
+
end
|
77
|
+
|
78
|
+
# get the layout description for internal use in {Bitfield}
|
79
|
+
# @api private
|
80
|
+
def result
|
81
|
+
dry_type = Dry::Types["coercible.hash"]
|
82
|
+
.schema(@schema)
|
83
|
+
.strict
|
84
|
+
.default(@default.freeze)
|
85
|
+
|
86
|
+
type = case @max
|
87
|
+
when 0..8
|
88
|
+
UInt8
|
89
|
+
when 9..16
|
90
|
+
UInt16
|
91
|
+
when 17..32
|
92
|
+
UInt32
|
93
|
+
when 32..64
|
94
|
+
UInt64
|
95
|
+
else
|
96
|
+
raise Error, "bitfields greater than 64 bits not supported"
|
97
|
+
end
|
98
|
+
|
99
|
+
[type, @fields, dry_type, @endian, @layout]
|
100
|
+
end
|
101
|
+
|
102
|
+
# set the endian for this {Bitfield}
|
103
|
+
# @param value [Symbol] `:big` or `:little`
|
104
|
+
def endian(value)
|
105
|
+
@endian = Endian[value]
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# skip `bits` bits in the layout of this bitfield
|
110
|
+
# @param bits [Integer] number of bits to skip
|
111
|
+
def skip(bits)
|
112
|
+
raise Error, "cannot mix `#skip` and `#field` in Bitfield layout" unless
|
113
|
+
@offset
|
114
|
+
@offset += bits
|
115
|
+
@max = @offset if @offset > @max
|
116
|
+
@layout << "skip #{bits}"
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
# set the alignment of the next field declared using {Builder#signed} or
|
121
|
+
# {Builder#unsigned}
|
122
|
+
# @param bits [Integer] bit alignment of the next field
|
123
|
+
# @note {Builder#align} cannot be mixed with calls to {Builder#field}
|
124
|
+
#
|
125
|
+
# @example
|
126
|
+
# class MyBits < CTypes::Bitfield
|
127
|
+
# layout do
|
128
|
+
# unsigned :a # single bit at offset 0
|
129
|
+
# align 4
|
130
|
+
# unsigned :b, 2 # two bits at offset 4
|
131
|
+
# align 4
|
132
|
+
# unsigned :c # single bit at offset 8
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
def align(bits)
|
137
|
+
raise Error, "cannot mix `#align` and `#field` in Bitfield layout" unless
|
138
|
+
@offset
|
139
|
+
@offset += bits - (@offset % bits)
|
140
|
+
@layout << "align #{bits}"
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
# append a new unsigned field to the bitfield
|
145
|
+
# @param name [String, Symbol] name of the field
|
146
|
+
# @param bits [Integer] number of bits
|
147
|
+
def unsigned(name, bits = 1)
|
148
|
+
unless @offset
|
149
|
+
raise Error,
|
150
|
+
"cannot mix `#unsigned` and `#field` in Bitfield layout"
|
151
|
+
end
|
152
|
+
|
153
|
+
name = name.to_sym
|
154
|
+
raise Error, "duplicate field: %p" % [name] if
|
155
|
+
@fields.any? { |(n, _)| n == name }
|
156
|
+
|
157
|
+
@layout << ((bits == 1) ?
|
158
|
+
"unsigned %p" % [name] :
|
159
|
+
"unsigned %p, %d" % [name, bits])
|
160
|
+
|
161
|
+
__field_impl(name:, bits:, offset: @offset, signed: false)
|
162
|
+
@offset += bits
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# append a new signed field to the bitfield
|
167
|
+
# @param name [String, Symbol] name of the field
|
168
|
+
# @param bits [Integer] number of bits
|
169
|
+
def signed(name, bits = 1)
|
170
|
+
unless @offset
|
171
|
+
raise Error,
|
172
|
+
"cannot mix `#signed` and `#field` in Bitfield layout"
|
173
|
+
end
|
174
|
+
|
175
|
+
name = name.to_sym
|
176
|
+
raise Error, "duplicate field: %p" % [name] if
|
177
|
+
@fields.any? { |(n, _)| n == name }
|
178
|
+
|
179
|
+
@layout << ((bits == 1) ?
|
180
|
+
"signed %p" % [name] :
|
181
|
+
"signed %p, %d" % [name, bits])
|
182
|
+
|
183
|
+
__field_impl(name:, bits:, offset: @offset, signed: true)
|
184
|
+
@offset += bits
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# set the size of the {Bitfield} in bytes
|
189
|
+
#
|
190
|
+
# Once the size is set, the Bitfield cannot grow past that size. Any calls
|
191
|
+
# to {Builder#signed} or {Builder#unsigned} that go beyond the size will
|
192
|
+
# raise errors.
|
193
|
+
#
|
194
|
+
# @param n [Integer] size in bytes
|
195
|
+
def bytes(n)
|
196
|
+
@layout << "bytes #{n}"
|
197
|
+
@max = n * 8
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
# declare a bit field at a specific offset
|
202
|
+
# @param name [String, Symbol] name of the field
|
203
|
+
# @param offset [Integer] right to left bit offset, where 0 is the least
|
204
|
+
# significant bit in a byte
|
205
|
+
# @param bits [Integer] number of bits used by this bitfield
|
206
|
+
# @param signed [Boolean] set to true to unpack as a signed integer
|
207
|
+
#
|
208
|
+
# This method is an alternative the construtive interface provided by
|
209
|
+
# {Builder#skip}, {Builder#align},
|
210
|
+
# {Builder#unsigned}, and {Builder#signed}. This is a programmatic
|
211
|
+
# interface for explicitly declaring fields using offset & bitcount.
|
212
|
+
#
|
213
|
+
# @note This method should not be used in combination with
|
214
|
+
# {Builder#skip}, {Builder#align}, {Builder#unsigned}, {Builder#signed}.
|
215
|
+
def field(name, offset:, bits:, signed: false)
|
216
|
+
name = name.to_sym
|
217
|
+
raise Error, "duplicate field: %p" % [name] if
|
218
|
+
@fields.any? { |(n, _)| n == name }
|
219
|
+
|
220
|
+
@layout << "field %p, offset: %d, bits: %d, signed: %p" %
|
221
|
+
[name, offset, bits, signed]
|
222
|
+
@offset = nil
|
223
|
+
|
224
|
+
__field_impl(name:, offset:, bits:, signed:)
|
225
|
+
self
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
# @api private
|
231
|
+
def __field_impl(name:, offset:, bits:, signed:) # :nodoc:
|
232
|
+
mask = (1 << bits) - 1
|
233
|
+
|
234
|
+
schema = Dry::Types["integer"].default(0)
|
235
|
+
@schema[name] = if signed
|
236
|
+
schema.constrained(gteq: 0 - (1 << (bits - 1)), lt: 1 << (bits - 1))
|
237
|
+
else
|
238
|
+
schema.constrained(gteq: 0, lt: 1 << bits)
|
239
|
+
end
|
240
|
+
@default[name] = 0
|
241
|
+
|
242
|
+
@fields << [name, offset, mask, signed ? bits : nil, :"@#{name}"]
|
243
|
+
@max = offset + bits if offset + bits > @max
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|