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
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
module CTypes
|
7
|
+
# Used to export CTypes
|
8
|
+
# @api private
|
9
|
+
class Exporter
|
10
|
+
def initialize(output = "".dup)
|
11
|
+
@output = output
|
12
|
+
@indent = 0
|
13
|
+
@indented = false
|
14
|
+
@type_lookup = nil
|
15
|
+
end
|
16
|
+
attr_writer :type_lookup
|
17
|
+
attr_reader :output
|
18
|
+
|
19
|
+
def nest(indent, &block)
|
20
|
+
@indent += indent
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
@indent -= indent
|
24
|
+
end
|
25
|
+
|
26
|
+
def break
|
27
|
+
self << "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(arg)
|
31
|
+
case arg
|
32
|
+
when CTypes::Type
|
33
|
+
if buf = @type_lookup&.call(arg)
|
34
|
+
self << buf
|
35
|
+
else
|
36
|
+
nest(2) { arg.export_type(self) }
|
37
|
+
end
|
38
|
+
when ::String
|
39
|
+
unless @indented
|
40
|
+
@output << " " * @indent
|
41
|
+
@indented = true
|
42
|
+
end
|
43
|
+
@output << arg
|
44
|
+
@indented = !arg.end_with?("\n")
|
45
|
+
else
|
46
|
+
raise Error, "not supported"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require "dry-types"
|
7
|
+
|
8
|
+
module CTypes
|
9
|
+
module Helpers
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# define integer types
|
13
|
+
[8, 16, 32, 64, 128].each do |bits|
|
14
|
+
define_method("uint%d" % bits) { CTypes.const_get("UInt#{bits}") }
|
15
|
+
define_method("int%d" % bits) { CTypes.const_get("Int#{bits}") }
|
16
|
+
end
|
17
|
+
|
18
|
+
# create an {Enum} type
|
19
|
+
# @param type [Type] integer type to encode as; default uint32
|
20
|
+
# @param values [Array, Hash] value names, or name-value pairs
|
21
|
+
#
|
22
|
+
# @example 8-bit enum with two known values
|
23
|
+
# t = enum(uint8, [:a, :b])
|
24
|
+
# t.pack(:a) # => "\x00"
|
25
|
+
# t.pack(:b) # => "\x01"
|
26
|
+
# t.pack(:c) # Dry::Types::ConstraintError
|
27
|
+
#
|
28
|
+
# @example sparse 32-bit enum
|
29
|
+
# t = enum(uint32, {none: 0, a: 0x1000, b: 0x20})
|
30
|
+
# t.pack(:none) # => "\0\0\0\0"
|
31
|
+
# t.pack(:a) # => "\x00\x10\x00\x00" (little endian)
|
32
|
+
# t.pack(:b) # => "\x20\x00\x00\x00" (little endian)
|
33
|
+
#
|
34
|
+
# @example sparse 32-bit enum using builder
|
35
|
+
# t = enum(uint16) do |e|
|
36
|
+
# e << %i{a b c} # a = 0, b = 1, c = 2
|
37
|
+
# e << {d: 16} # d = 16
|
38
|
+
# e << :e # e = 17
|
39
|
+
# end
|
40
|
+
# t.pack(:e) # => "\x11\x00" (little endian)
|
41
|
+
def enum(type = nil, values = nil, &)
|
42
|
+
Enum.new(type, values, &)
|
43
|
+
end
|
44
|
+
|
45
|
+
# create a {Bitmap} type
|
46
|
+
# @param type [Type] integer type to encode as; default min bytes required
|
47
|
+
# @param bits [Hash, Enum] map of names to bit position
|
48
|
+
#
|
49
|
+
# @example 32-bit bitmap
|
50
|
+
# bitmap({a: 0, b: 1, c: 2}) # => #<Bitmap ...>
|
51
|
+
#
|
52
|
+
# @example 32-bit bitmap using block syntax; same as [Enum]
|
53
|
+
# bitmap do |b|
|
54
|
+
# b << :a
|
55
|
+
# b << :b
|
56
|
+
# end # => #<Bitmap a: 0, b: 1>
|
57
|
+
#
|
58
|
+
# @example 8-bit bitmap
|
59
|
+
# bitmap(uint8, {a: 0, b: 1}) # => #<Bitmap a: 0, b: 1>
|
60
|
+
#
|
61
|
+
def bitmap(type = nil, bits = nil, &)
|
62
|
+
if bits.nil?
|
63
|
+
bits = type
|
64
|
+
type = uint32
|
65
|
+
end
|
66
|
+
|
67
|
+
bits = enum(bits, &) unless bits.is_a?(Enum)
|
68
|
+
Bitmap.new(type: type, bits: bits)
|
69
|
+
end
|
70
|
+
|
71
|
+
# create a {String} type
|
72
|
+
# @param size [Integer] optional string size in bytes
|
73
|
+
# @param trim [Boolean] set to false to preserve trailing null bytes when
|
74
|
+
# unpacking
|
75
|
+
#
|
76
|
+
# @example 5 byte string
|
77
|
+
# s = string(5)
|
78
|
+
# s.unpack("hello world") # => "hello")
|
79
|
+
def string(size = nil, trim: true)
|
80
|
+
String.new(size:, trim:)
|
81
|
+
end
|
82
|
+
|
83
|
+
# create a {Struct} type
|
84
|
+
# @param attributes [Hash] name/type attribute pairs
|
85
|
+
# @yield block passed to {Struct::Builder}
|
86
|
+
#
|
87
|
+
# @example hash syntax
|
88
|
+
# t = struct(id: uint32, name: string.terminated)
|
89
|
+
# t.pack({id: 1, name: "Karlach"}) # => "\1\0\0\0Karlach\0"
|
90
|
+
#
|
91
|
+
# @example block syntax
|
92
|
+
# t = struct do
|
93
|
+
# attribute :id, uint32
|
94
|
+
# attrubite :name, string.terminated
|
95
|
+
# end
|
96
|
+
# t.pack({id: 1, name: "Karlach"}) # => "\1\0\0\0Karlach\0"
|
97
|
+
def struct(attributes = nil, &block)
|
98
|
+
Class.new(Struct) do
|
99
|
+
if attributes
|
100
|
+
layout do
|
101
|
+
attributes.each do |name, type|
|
102
|
+
attribute name, type
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
layout(&block)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# create a {Union} type
|
112
|
+
# @param members [Hash] name/type member pairs
|
113
|
+
# @yield block bassed to {Union::Builder}
|
114
|
+
#
|
115
|
+
# @example hash syntax
|
116
|
+
# t = union(word: uint32, halfword: uint16, byte: uint8)
|
117
|
+
# t.pack({byte: 3}) # => "\x03\x00\x00\x00"
|
118
|
+
#
|
119
|
+
# @example block syntax
|
120
|
+
# t = union do
|
121
|
+
# member :word, uint32
|
122
|
+
# member :halfword, uint16
|
123
|
+
# member :byte, uint8
|
124
|
+
# end
|
125
|
+
# t.pack({byte: 3}) # => "\x03\x00\x00\x00"
|
126
|
+
def union(members = nil, &block)
|
127
|
+
Class.new(Union) do
|
128
|
+
if members
|
129
|
+
layout do
|
130
|
+
members.each do |name, type|
|
131
|
+
member name, type
|
132
|
+
end
|
133
|
+
end
|
134
|
+
else
|
135
|
+
layout(&block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# create an {Array} type
|
141
|
+
# @param type [Type] data type contained within the array
|
142
|
+
# @param size [Integer] optional array size; no size implies greedy array
|
143
|
+
# @param terminator optional unpacked value that represents the array
|
144
|
+
# terminator
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# # greedy array of uint32 values
|
148
|
+
# array(uint32)
|
149
|
+
# # array of 4 uint8 values
|
150
|
+
# array(uint8, 4)
|
151
|
+
# # array of signed 32-bit integers terminated with a -1
|
152
|
+
# array(int32, terminator: -1)
|
153
|
+
def array(type, size = nil, terminator: nil)
|
154
|
+
Array.new(type:, size:, terminator:)
|
155
|
+
end
|
156
|
+
|
157
|
+
# create a {Bitfield} type
|
158
|
+
# @param type [Type] type to use for packed representation
|
159
|
+
# @param bits [Hash] map of name to bit count
|
160
|
+
# @yield block passed to {Bitfield::Builder}
|
161
|
+
#
|
162
|
+
# @example dynamically sized
|
163
|
+
# t = bitfield(a: 1, b: 2, c: 3)
|
164
|
+
# t.pack({c: 0b111}) # => "\x38" (0b00111000)
|
165
|
+
#
|
166
|
+
# @example fixed size to pad to 16 bits.
|
167
|
+
# t = bitfield(uint16, a: 1, b: 2, c: 3)
|
168
|
+
# t.pack({c: 0b111}) # => "\x38\x00" (0b00111000_00000000)
|
169
|
+
#
|
170
|
+
def bitfield(type = nil, bits = nil, &block)
|
171
|
+
if bits.nil? && !block
|
172
|
+
bits = type
|
173
|
+
type = nil
|
174
|
+
end
|
175
|
+
|
176
|
+
Class.new(Bitfield) do
|
177
|
+
if bits
|
178
|
+
layout do
|
179
|
+
bytes(type.size) if type
|
180
|
+
bits.each do |name, size|
|
181
|
+
unsigned name, size
|
182
|
+
end
|
183
|
+
end
|
184
|
+
else
|
185
|
+
layout(&block)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require "nokogiri"
|
5
|
+
|
6
|
+
module CTypes
|
7
|
+
class Importers::CastXML::Loader
|
8
|
+
include CTypes::Helpers
|
9
|
+
|
10
|
+
def initialize(io)
|
11
|
+
@doc = Nokogiri.parse(io)
|
12
|
+
@xml = @doc.xpath("//castxml[1]")&.first or
|
13
|
+
raise Error, "<castxml> node not found"
|
14
|
+
@nodes = @xml.xpath("./*[@id]").each_with_object({}) { |n, o|
|
15
|
+
o[n[:id]] = n
|
16
|
+
}
|
17
|
+
@ctypes = {}
|
18
|
+
end
|
19
|
+
attr_reader :xml
|
20
|
+
|
21
|
+
def load
|
22
|
+
m = Module.new
|
23
|
+
load_into(m)
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_into(namespace)
|
27
|
+
@xml.children.each do |node|
|
28
|
+
next unless node.element?
|
29
|
+
|
30
|
+
case node.name
|
31
|
+
when "typedef", "struct", "union", "array", "enumeration"
|
32
|
+
# skip builtin types
|
33
|
+
next if node[:file] == "f0"
|
34
|
+
|
35
|
+
name, type = ctype(node[:id])
|
36
|
+
next if name.empty?
|
37
|
+
|
38
|
+
namespace.define_singleton_method(name) { type }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
namespace
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def ctype(id)
|
48
|
+
return @ctypes[id] if @ctypes.has_key?(id)
|
49
|
+
node = @nodes[id] or raise Error, "node not found: id=\"#{id}\""
|
50
|
+
|
51
|
+
return node[:name], nil if node[:incomplete] == "1"
|
52
|
+
|
53
|
+
type = case node.name
|
54
|
+
when "fundamentaltype"
|
55
|
+
unsigned = node[:name].include?("unsigned")
|
56
|
+
case node[:size]
|
57
|
+
when "0"
|
58
|
+
nil
|
59
|
+
when "128"
|
60
|
+
array(unsigned ? uint64 : int64, 2)
|
61
|
+
when "64"
|
62
|
+
unsigned ? uint64 : int64
|
63
|
+
when "32"
|
64
|
+
unsigned ? uint32 : int32
|
65
|
+
when "16"
|
66
|
+
unsigned ? uint16 : int16
|
67
|
+
when "8"
|
68
|
+
unsigned ? uint8 : int8
|
69
|
+
else
|
70
|
+
raise Error, "unknown FundamentalType: %s" % node.pretty_inspect
|
71
|
+
end
|
72
|
+
when "typedef", "field", "elaboratedtype", "cvqualifiedtype"
|
73
|
+
_, t = ctype(node[:type])
|
74
|
+
t
|
75
|
+
when "struct"
|
76
|
+
if node.has_attribute?("members")
|
77
|
+
pos = 0
|
78
|
+
members = node[:members].split.each_with_object({}) do |mid, o|
|
79
|
+
# to support member alignment, we need to do some extra work here
|
80
|
+
# to add padding members to structures when there are gaps. Note
|
81
|
+
# that for some reason, anonymous structs are not counted towards
|
82
|
+
# the offset of following struct fields; this may be a bug in llvm,
|
83
|
+
# as castxml just prints what was provided.
|
84
|
+
mem = @nodes[mid] or raise Error, "node not found: id=\"#{mid}\""
|
85
|
+
if mem.has_attribute?("offset")
|
86
|
+
|
87
|
+
# add a padding member to the struct if needed
|
88
|
+
offset = mem[:offset].to_i
|
89
|
+
if pos < offset
|
90
|
+
o[:"__pad_#{pos / 8}"] =
|
91
|
+
string((offset - pos) / 8, trim: false)
|
92
|
+
end
|
93
|
+
|
94
|
+
# always set pos to the current offset; this handles the case
|
95
|
+
# where we added the size of a nested anonymous struct, but llvm
|
96
|
+
# does not appear to.
|
97
|
+
pos = offset
|
98
|
+
end
|
99
|
+
|
100
|
+
name, mtype = ctype(mid)
|
101
|
+
o[name.to_sym] = mtype unless name.empty?
|
102
|
+
pos += mtype.size * 8
|
103
|
+
end
|
104
|
+
else
|
105
|
+
members = {unknown: array(uint8, node[:size].to_i)}
|
106
|
+
end
|
107
|
+
struct(members)
|
108
|
+
when "union"
|
109
|
+
members = node[:members].split.each_with_object({}) do |mid, o|
|
110
|
+
name, mtype = ctype(mid)
|
111
|
+
o[name.to_sym] = mtype
|
112
|
+
end
|
113
|
+
union(members)
|
114
|
+
when "arraytype"
|
115
|
+
n, t = ctype(node[:type])
|
116
|
+
if n == "char"
|
117
|
+
string(node[:max].to_i + 1)
|
118
|
+
else
|
119
|
+
array(t, node[:max].to_i + 1)
|
120
|
+
end
|
121
|
+
when "enumeration"
|
122
|
+
values = node.children.each_with_object({}) do |v, o|
|
123
|
+
o[v[:name].downcase] = v[:init].to_i if v.name == "enumvalue"
|
124
|
+
end
|
125
|
+
type = if node[:type]
|
126
|
+
_, t = ctype(node[:type])
|
127
|
+
t
|
128
|
+
elsif node[:size] == "32"
|
129
|
+
uint32
|
130
|
+
else
|
131
|
+
raise Error, "unsupported enum node: %p" % node
|
132
|
+
end
|
133
|
+
enum(type, values)
|
134
|
+
when "pointertype"
|
135
|
+
case node[:size]
|
136
|
+
when "64"
|
137
|
+
uint64
|
138
|
+
when "32"
|
139
|
+
uint32
|
140
|
+
else
|
141
|
+
raise Error, "unknown PointerType size: %s" % node.pretty_inspect
|
142
|
+
end
|
143
|
+
else
|
144
|
+
raise "unsupported node: %s" % node.pretty_inspect
|
145
|
+
end
|
146
|
+
|
147
|
+
@ctypes[id] = [node[:name], type]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require "open3"
|
5
|
+
begin
|
6
|
+
require "nokogiri"
|
7
|
+
rescue LoadError
|
8
|
+
puts <<~ERR
|
9
|
+
WARNING: Failed to require `nokogiri` gem.
|
10
|
+
|
11
|
+
`nokogiri` is required to parse CastXML output. To use the CastXML
|
12
|
+
importer, please install the gem.
|
13
|
+
ERR
|
14
|
+
end
|
15
|
+
|
16
|
+
module CTypes
|
17
|
+
module Importers
|
18
|
+
module CastXML
|
19
|
+
class CompilerError < CTypes::Error; end
|
20
|
+
|
21
|
+
def self.load_xml(xml)
|
22
|
+
io = case xml
|
23
|
+
when IO
|
24
|
+
xml
|
25
|
+
when ::String
|
26
|
+
StringIO.new(xml)
|
27
|
+
else
|
28
|
+
raise Error, "arg must be IO or String: %p" % xml
|
29
|
+
end
|
30
|
+
|
31
|
+
l = Loader.new(io)
|
32
|
+
l.load
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.load_xml_file(path)
|
36
|
+
File.open(path) do |f|
|
37
|
+
load_xml(f)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load_source(src)
|
42
|
+
Tempfile.open(["", ".c"]) do |f|
|
43
|
+
f.write(src)
|
44
|
+
f.flush
|
45
|
+
load_source_file(f.path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.load_source_file(path)
|
50
|
+
stdout, stderr, status = Open3
|
51
|
+
.capture3("castxml --castxml-output=1 #{path} -o -")
|
52
|
+
raise CompilerError, stderr unless status.success?
|
53
|
+
load_xml(stdout)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
require_relative "castxml/loader"
|
data/lib/ctypes/int.rb
ADDED
@@ -0,0 +1,147 @@
|
|
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
|
+
# Handles packing and unpacking of integer types of various lengths and
|
9
|
+
# signed-ness. The most common integer sizes have been declared as
|
10
|
+
# constants:
|
11
|
+
# - unsigned: {UInt64}, {UInt32}, {UInt16}, {UInt8}
|
12
|
+
# - signed: {Int64}, {Int32}, {Int16}, {Int8}
|
13
|
+
# or their respective helpers in {Helpers}.
|
14
|
+
class Int
|
15
|
+
include Type
|
16
|
+
|
17
|
+
# initialize an {Int} type
|
18
|
+
#
|
19
|
+
# @param [Integer] bits number of bits in the integer
|
20
|
+
# @param [Boolean] signed set to true if integer is signed
|
21
|
+
# @param [String] format {::Array#pack} format for integer
|
22
|
+
# @param [String] desc human-readable description of type
|
23
|
+
#
|
24
|
+
def initialize(bits:, signed:, format:, desc:)
|
25
|
+
type = Dry::Types["integer"].default(0)
|
26
|
+
@signed = !!signed
|
27
|
+
if @signed
|
28
|
+
@min = 0 - (1 << (bits - 1))
|
29
|
+
@max = 1 << (bits - 1) - 1
|
30
|
+
else
|
31
|
+
@min = 0
|
32
|
+
@max = (1 << bits) - 1
|
33
|
+
end
|
34
|
+
@dry_type = type.constrained(gteq: @min, lteq: @max)
|
35
|
+
@size = bits / 8
|
36
|
+
if @size > 1
|
37
|
+
@format_big = "#{format}>"
|
38
|
+
@format_little = "#{format}<"
|
39
|
+
else
|
40
|
+
@format_big = @format_little = format.to_s
|
41
|
+
end
|
42
|
+
@fmt = (@size > 1) ? {big: "#{format}>", little: "#{format}<"} :
|
43
|
+
{big: format.to_s, little: format.to_s}
|
44
|
+
@desc = desc
|
45
|
+
end
|
46
|
+
attr_reader :size, :min, :max
|
47
|
+
|
48
|
+
# convert an Integer into a String containing the binary representation of
|
49
|
+
# that number for the given type.
|
50
|
+
#
|
51
|
+
# @param value [Integer] number to pack
|
52
|
+
# @param endian [Symbol] byte order
|
53
|
+
# @param validate [Boolean] set to false to disable bounds checking
|
54
|
+
# @return [String] binary encoding for value
|
55
|
+
#
|
56
|
+
# @example pack a uint32_t using the native endian
|
57
|
+
# CTypes::UInt32.pack(0x12345678) # => "\x78\x56\x34\x12"
|
58
|
+
#
|
59
|
+
# @example pack a big endian uint32_t
|
60
|
+
# CTypes::UInt32.pack(endian: :big) # => "\x12\x34\x56\x78"
|
61
|
+
#
|
62
|
+
# @example pack a fixed-endian (big) uint32_t
|
63
|
+
# t = UInt32.with_endian(:big)
|
64
|
+
# t.pack(0x12345678) # => "\x12\x34\x56\x78"
|
65
|
+
#
|
66
|
+
# @see CTypes::Type#pack
|
67
|
+
def pack(value, endian: default_endian, validate: true)
|
68
|
+
value = (value.nil? ? @dry_type[] : @dry_type[value]) if validate
|
69
|
+
endian ||= default_endian
|
70
|
+
[value].pack(@fmt[endian])
|
71
|
+
end
|
72
|
+
|
73
|
+
# decode an Integer from the byte String provided, returning both the
|
74
|
+
# Integer and any unused bytes in the String
|
75
|
+
#
|
76
|
+
# @param buf [String] bytes to be unpacked
|
77
|
+
# @param endian [Symbol] endian of data within buf
|
78
|
+
# @return [Integer, String] decoded Integer, and remaining bytes
|
79
|
+
#
|
80
|
+
# @example pack a uint32_t using the native endian
|
81
|
+
# CTypes::UInt32.pack(0x12345678) # => "\x78\x56\x34\x12"
|
82
|
+
#
|
83
|
+
# @example pack a big endian uint32_t
|
84
|
+
# CTypes::UInt32.pack(endian: :big) # => "\x12\x34\x56\x78"
|
85
|
+
#
|
86
|
+
# @example pack a fixed-endian (big) uint32_t
|
87
|
+
# t = UInt32.with_endian(:big)
|
88
|
+
# t.pack(0x12345678) # => "\x12\x34\x56\x78"
|
89
|
+
#
|
90
|
+
# @see CTypes::Type#unpack
|
91
|
+
# @see CTypes::Type#unpack_one
|
92
|
+
def unpack_one(buf, endian: default_endian)
|
93
|
+
endian ||= default_endian # override nil
|
94
|
+
value = buf.unpack1(@fmt[endian]) or
|
95
|
+
raise missing_bytes_error(input: buf, need: @size)
|
96
|
+
[value, buf.byteslice(@size..)]
|
97
|
+
end
|
98
|
+
|
99
|
+
# @api private
|
100
|
+
def greedy?
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
104
|
+
# @api private
|
105
|
+
def signed?
|
106
|
+
@signed
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def pretty_print(q) # :nodoc:
|
111
|
+
if @endian
|
112
|
+
q.text(@desc + ".with_endian(%p)" % @endian)
|
113
|
+
else
|
114
|
+
q.text(@desc)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
alias_method :inspect, :pretty_inspect # :nodoc:
|
118
|
+
|
119
|
+
# @api private
|
120
|
+
def export_type(q) # :nodoc:
|
121
|
+
q << @desc
|
122
|
+
q << ".with_endian(%p)" % [@endian] if @endian
|
123
|
+
end
|
124
|
+
|
125
|
+
# @api private
|
126
|
+
def type_name
|
127
|
+
"#{@desc}_t"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# base type for unsiged 8-bit integers
|
132
|
+
UInt8 = Int.new(bits: 8, signed: false, format: "C", desc: "uint8")
|
133
|
+
# base type for unsiged 16-bit integers
|
134
|
+
UInt16 = Int.new(bits: 16, signed: false, format: "S", desc: "uint16")
|
135
|
+
# base type for unsiged 32-bit integers
|
136
|
+
UInt32 = Int.new(bits: 32, signed: false, format: "L", desc: "uint32")
|
137
|
+
# base type for unsiged 64-bit integers
|
138
|
+
UInt64 = Int.new(bits: 64, signed: false, format: "Q", desc: "uint64")
|
139
|
+
# base type for siged 8-bit integers
|
140
|
+
Int8 = Int.new(bits: 8, signed: true, format: "c", desc: "int8")
|
141
|
+
# base type for siged 16-bit integers
|
142
|
+
Int16 = Int.new(bits: 16, signed: true, format: "s", desc: "int16")
|
143
|
+
# base type for siged 32-bit integers
|
144
|
+
Int32 = Int.new(bits: 32, signed: true, format: "l", desc: "int32")
|
145
|
+
# base type for siged 64-bit integers
|
146
|
+
Int64 = Int.new(bits: 64, signed: true, format: "q", desc: "int64")
|
147
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: 2025 Cisco
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
module CTypes
|
7
|
+
# Exception raised when attempting to unpack a {Type} that requires more
|
8
|
+
# bytes than were provided in the input.
|
9
|
+
class MissingBytesError < Error
|
10
|
+
def initialize(type:, input:, need:)
|
11
|
+
@type = type
|
12
|
+
@input = input
|
13
|
+
@need = need
|
14
|
+
super("insufficent input to unpack %s; missing %d bytes" %
|
15
|
+
[@type, missing])
|
16
|
+
end
|
17
|
+
attr_reader :type, :input, :need
|
18
|
+
|
19
|
+
# get the number of additional bytes required to unpack this type
|
20
|
+
def missing
|
21
|
+
@need - @input.size
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ctypes/pad.rb
ADDED
@@ -0,0 +1,56 @@
|
|
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
|
+
# Generic type to represent a gap in the data structure. Unpacking this
|
9
|
+
# type will consume the pad size and return nil. Packing this type will
|
10
|
+
# return a string of null bytes of the appropriate size.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# t = Pad.new(4)
|
14
|
+
# t.unpack_one("hello_world) # => [nil, "o_world"]
|
15
|
+
# t.pack("blahblahblah") # => "\0\0\0\0"
|
16
|
+
class Pad
|
17
|
+
include Type
|
18
|
+
|
19
|
+
def initialize(size)
|
20
|
+
@size = size
|
21
|
+
@dry_type = Dry::Types::Any.default(nil)
|
22
|
+
end
|
23
|
+
attr_reader :size
|
24
|
+
|
25
|
+
def pack(value, endian: default_endian, validate: true)
|
26
|
+
"\0" * @size
|
27
|
+
end
|
28
|
+
|
29
|
+
def unpack_one(buf, endian: default_endian)
|
30
|
+
raise missing_bytes_error(input: buf, need: @size) if
|
31
|
+
@size && buf.size < @size
|
32
|
+
[nil, buf.byteslice(@size..)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def greedy?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"pad(%d)" % [@size]
|
41
|
+
end
|
42
|
+
|
43
|
+
def pretty_print(q)
|
44
|
+
q.text("pad(%d)" % @size)
|
45
|
+
end
|
46
|
+
alias_method :inspect, :pretty_inspect # :nodoc:
|
47
|
+
|
48
|
+
def export_type(q)
|
49
|
+
q << ".pad(%d)" % [@size]
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
other.is_a?(self.class) && other.size == size
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|