Class: CTypes::Bitfield

Inherits:
Object
  • Object
show all
Extended by:
Type
Defined in:
lib/ctypes/bitfield.rb

Overview

define a bit-field type for use in structures

Examples:

8-bit integer split into three multibit values

class MyBits < CTypes::Bitfield
  # declare bit layout in right-to-left order
  layout do
    unsigned :bit             # single bit for a
    skip 1                    # skip one bit
    unsigned :two, 2          # two bits for this field
    signed :nibble, 4         # four bit nibble as a signed int
  end
end

# size is the number of bytes required by the layout, but can be larger
# when requested in #layout
MyBits.size                   # => 1

# The bits are packed right-to-left
MyBits.pack({bit: 1})         # => "\x01" (0b00000001)
MyBits.pack({two: 3})         # => "\x0c" (0b00001100)
MyBits.pack({nibble: -1})     # => "\xf0" (0b11110000)

# unpack a value and access individual fields
value = MyBits.unpack("\xf1") # => #<Bitfield bit: 1, two: 0, nibble: -1>
value.bit                     # => 1
value.two                     # => 0
value.nibble                  # => -1

# update the value and repack
value.two = 1
value.nibble = 0
value.to_binstr               # => "\x05" (0b00000101)

Defined Under Namespace

Classes: Builder

Instance Attribute Summary

Attributes included from Type

#dry_type, #endian

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Type

default_endian, default_value, fixed_size?, greedy?, pack, pread, read, unpack, unpack_all, unpack_one, with_endian, without_endian

Class Method Details

.==(other) ⇒ Object



195
196
197
198
199
200
201
# File 'lib/ctypes/bitfield.rb', line 195

def self.==(other)
  return true if super
  return false unless other.is_a?(Class) && other < Bitfield
  other.field_layout == @bits &&
    other.default_endian == default_endian &&
    other.size == size
end

._newBitfield

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

allocate an uninitialized instance of the bitfield

Returns:

  • (Bitfield)

    uninitialized bitfield instance



183
# File 'lib/ctypes/bitfield.rb', line 183

alias_method :_new, :new

.builderObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



74
75
76
# File 'lib/ctypes/bitfield.rb', line 74

def self.builder
  Builder.new
end

.export_type(q) ⇒ Object

generate ruby code needed to create this type



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ctypes/bitfield.rb', line 165

def self.export_type(q)
  q << "bitfield {"
  q.break
  q.nest(2) do
    @layout.each do |cmd|
      q << cmd
      q.break
    end
  end
  q << "}"
  q << ".with_endian(%p)" % [@endian] if @endian
end

.field_layoutObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



204
205
206
# File 'lib/ctypes/bitfield.rb', line 204

def self.field_layout # :nodoc:
  @bits
end

.fieldsObject

get the list of fields defined in this bitfield



144
145
146
# File 'lib/ctypes/bitfield.rb', line 144

def self.fields
  @fields.keys
end

.fixed_size?Boolean

check if bitfield is a fixed size

Returns:

  • (Boolean)


101
102
103
# File 'lib/ctypes/bitfield.rb', line 101

def self.fixed_size?
  true
end

.greedy?Boolean

check if bitfield is greedy

Returns:

  • (Boolean)


106
107
108
# File 'lib/ctypes/bitfield.rb', line 106

def self.greedy?
  false
end

.has_field?(name) ⇒ Boolean

check if the bitfield declared a specific field

Returns:

  • (Boolean)


149
150
151
# File 'lib/ctypes/bitfield.rb', line 149

def self.has_field?(name)
  @fields.has_key?(name)
end

.layout(&block) ⇒ Object

describe the layout of the bitfield

Examples:

right-to-left bit layout

layout do
  unsigned :bit             # single bit for a
  skip 1                    # skip one bit
  unsigned :two, 2          # two bits for this field
  signed :nibble, 4         # four bit nibble as a signed int
end

explicit bit layout

layout do
  field :bit, offset: 0, bits: 1
  field :two, offset: 2, bits: 2
  field :nibble, offset: 4, bits: 4
end

create a two-byte bitfield, but only one bit declared

layout do
  size 2                    # bitfield will take two bytes
  field :bit, offset: 9, bits: 1
end

Raises:



66
67
68
69
70
71
# File 'lib/ctypes/bitfield.rb', line 66

def self.layout(&block)
  raise Error, "no block given" unless block
  builder = Builder.new(&block)
  builder.instance_eval(&block)
  apply_layout(builder)
end

.new(fields = nil) ⇒ Bitfield

allocate an instance of the Bitfield and initialize default values

Parameters:

  • fields (Hash) (defaults to: nil)

    values to set

Returns:



190
191
192
193
# File 'lib/ctypes/bitfield.rb', line 190

def self.new(fields = nil)
  buf = fields.nil? ? ("\0" * size) : pack(fields)
  unpack(buf)
end

.pack(value, endian: default_endian, validate: true) ⇒ ::String

pack a ruby hash containing bitfield values into a binary string

Parameters:

  • value (Hash)

    value to be encoded

  • endian (Symbol) (defaults to: default_endian)

    optional endian override

  • validate (Boolean) (defaults to: true)

    set to false to disable value validation

Returns:

  • (::String)

    binary encoding for value



115
116
117
118
119
120
121
122
123
# File 'lib/ctypes/bitfield.rb', line 115

def self.pack(value, endian: default_endian, validate: true)
  value = value.to_hash.freeze
  value = @dry_type[value] unless validate == false
  out = 0
  @bits.each do |(name, offset, mask, _)|
    out |= (value[name] & mask) << offset
  end
  @type.pack(out, endian:, validate:)
end

.pretty_print(q) ⇒ Object



153
154
155
156
157
158
159
# File 'lib/ctypes/bitfield.rb', line 153

def self.pretty_print(q)
  q.ctype("bitfield", @endian) do
    q.seplist(@layout, -> { q.breakable(";") }) do |cmd|
      q.text(cmd)
    end
  end
end

.sizeObject

get the size of the bitfield in bytes



96
97
98
# File 'lib/ctypes/bitfield.rb', line 96

def self.size
  @type&.size
end

.unpack_one(buf, endian: default_endian) ⇒ Bitfield

convert a String containing the binary represention of a c type into the equivalent ruby type

Parameters:

  • buf (::String)

    bytes that make up the type

  • endian (Symbol) (defaults to: default_endian)

    endian of data within buf

Returns:



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ctypes/bitfield.rb', line 131

def self.unpack_one(buf, endian: default_endian)
  value, rest = @type.unpack_one(buf, endian:)
  out = _new
  @bits.each do |(name, offset, mask, signed, var)|
    v = (value >> offset) & mask
    v |= (-1 << signed) | v if signed && v[signed - 1] == 1
    out.instance_variable_set(var, v)
  end

  [out, rest]
end

Instance Method Details

#==(other) ⇒ Object

Note:

this implementation also supports Hash equality through #to_h

determine if this instance of the bitfield is equal to another instance



263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/ctypes/bitfield.rb', line 263

def ==(other)
  case other
  when self.class
    self.class.field_layout.all? do |name, _, _, _, var|
      instance_variable_get(var) == other[name]
    end
  when Hash
    other == to_h
  else
    super
  end
end

#[](k) ⇒ Object

get an bitfield value

Parameters:

  • k (Symbol)

    field name

Returns:

  • value



219
220
221
222
# File 'lib/ctypes/bitfield.rb', line 219

def [](k)
  has_field!(k)
  instance_variable_get(:"@#{k}")
end

#[]=(k, v) ⇒ Object

set a bitfield value

Parameters:

  • k (Symbol)

    field name

  • v

    value



211
212
213
214
# File 'lib/ctypes/bitfield.rb', line 211

def []=(k, v)
  has_field!(k)
  instance_variable_set(:"@#{k}", v)
end

#has_key?(name) ⇒ Boolean

Returns:

  • (Boolean)


224
225
226
# File 'lib/ctypes/bitfield.rb', line 224

def has_key?(name)
  self.class.has_field?(name)
end

#pretty_print(q) ⇒ Object

:nodoc:



243
244
245
246
247
248
249
250
251
# File 'lib/ctypes/bitfield.rb', line 243

def pretty_print(q) # :nodoc:
  q.group(4, "bitfield {", "}") do
    q.seplist(self.class.field_layout, -> { q.breakable("") }) do |name, _|
      q.text(".#{name} = ")
      q.pp(instance_variable_get(:"@#{name}"))
      q.text(", ")
    end
  end
end

#to_binstr(endian: self.class.default_endian) ⇒ ::String

return the binary representation of this Bitfield instance

Returns:

  • (::String)

    binary representation of struct



256
257
258
# File 'lib/ctypes/bitfield.rb', line 256

def to_binstr(endian: self.class.default_endian)
  self.class.pack(to_h, endian:)
end

#to_h(shallow: false) ⇒ Object Also known as: to_hash



234
235
236
237
238
239
240
# File 'lib/ctypes/bitfield.rb', line 234

def to_h(shallow: false)
  out = {}
  self.class.field_layout.each do |name, _, _, _, var|
    out[name] = instance_variable_get(var)
  end
  out
end