class BitStruct

Class for packed binary data, with defined bitfields and accessors for them. See intro.txt for an overview.

Data after the end of the defined fields is accessible using the rest declaration. See examples/ip.rb. Nested fields can be declared using nest. See examples/nest.rb.

Note that all string methods are still available: length, grep, etc. The String#replace method is useful.

Constants

NULL_FIELD
VERSION

Public Class Methods

inherited(cl) click to toggle source
# File lib/bit-struct/bit-struct.rb, line 33
def inherited cl
  cl.instance_eval do
    @initial_value = nil
    @closed = nil
    @rest_field = nil
    @note = nil
  end
end

field access methods

↑ top

Public Class Methods

add_field(name, length, opts = {}) click to toggle source

Add a field to the BitStruct (usually, this is only used internally).

# File lib/bit-struct/bit-struct.rb, line 61
def add_field(name, length, opts = {})
  round_byte_length ## just to make sure this has been calculated
  ## before adding anything

  name = name.to_sym

  if @closed
    raise ClosedClassError, "Cannot add field #{name}: " +
      "The definition of the #{self.inspect} BitStruct class is closed."
  end

  if fields.find {|f|f.name == name}
    raise FieldNameError, "Field #{name} is already defined as a field."
  end

  if instance_methods(true).find {|m| m == name}
    if opts[:allow_method_conflict] || opts["allow_method_conflict"]
      warn "Field #{name} is already defined as a method."
    else
      raise FieldNameError,"Field #{name} is already defined as a method."
    end
  end

  field_class = opts[:field_class]

  prev = fields[-1] || NULL_FIELD
  offset = prev.offset + prev.length
  field = field_class.new(offset, length, name, opts)
  field.add_accessors_to(self)
  fields << field
  own_fields << field
  @bit_length += field.length
  @round_byte_length = (bit_length/8.0).ceil

  if @initial_value
    diff = @round_byte_length - @initial_value.length
    if diff > 0
      @initial_value << "\0" * diff
    end
  end

  field
end
bit_length() click to toggle source

Length, in bits, of this object.

# File lib/bit-struct/bit-struct.rb, line 130
def bit_length
  @bit_length ||= fields.inject(0) {|a, f| a + f.length}
end
default_options(h = nil) click to toggle source

Get or set the hash of default options for the class, which apply to all fields. Changes take effect immediately, so can be used alternatingly with blocks of field declarations. If h is provided, update the default options with that hash. Default options are inherited.

This is especially useful with the :endian => val option.

# File lib/bit-struct/bit-struct.rb, line 121
def default_options h = nil
  @default_options ||= superclass.default_options.dup
  if h
    @default_options.merge! h
  end
  @default_options
end
field_by_name(name) click to toggle source
# File lib/bit-struct/bit-struct.rb, line 143
def field_by_name name
  @field_by_name ||= {}
  field = @field_by_name[name]
  unless field
    field = fields.find {|f| f.name == name}
    @field_by_name[name] = field if field
  end
  field
end
fields() click to toggle source

Return the list of fields for this class.

# File lib/bit-struct/bit-struct.rb, line 50
def fields
  @fields ||= self == BitStruct ? [] : superclass.fields.dup
end
own_fields() click to toggle source

Return the list of fields defined by this class, not inherited from the superclass.

# File lib/bit-struct/bit-struct.rb, line 56
def own_fields
  @own_fields ||= []
end
round_byte_length() click to toggle source

Length, in bytes (rounded up), of this object.

# File lib/bit-struct/bit-struct.rb, line 135
def round_byte_length
  @round_byte_length ||= (bit_length/8.0).ceil
end

Public Instance Methods

field_by_name(name) click to toggle source

Return the field with the given name.

# File lib/bit-struct/bit-struct.rb, line 165
def field_by_name name
  self.class.field_by_name name
end
fields() click to toggle source

Return the list of fields for this class.

# File lib/bit-struct/bit-struct.rb, line 155
def fields
  self.class.fields
end
rest_field() click to toggle source

Return the rest field for this class.

# File lib/bit-struct/bit-struct.rb, line 160
def rest_field
  self.class.rest_field
end

field declaration methods

↑ top

Public Class Methods

char(name, length, *rest) click to toggle source

Define a char string field in the current subclass of BitStruct, with the given name and length (in bits). Trailing nulls are considered part of the string.

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

Note that the accessors have COPY semantics, not reference.

# File lib/bit-struct/fields.rb, line 13
def char(name, length, *rest)
  opts = parse_options(rest, name, CharField)
  add_field(name, length, opts)
end
Also aliased as: string
float(name, length, *rest) click to toggle source

Define a floating point field in the current subclass of BitStruct, with the given name and length (in bits).

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

The :endian => :native option overrides the default of :network byte ordering, in favor of native byte ordering. Also permitted are :big (same as :network) and :little.

# File lib/bit-struct/fields.rb, line 32
def float name, length, *rest
  opts = parse_options(rest, name, FloatField)
  add_field(name, length, opts)
end
hex_octets(name, length, *rest) click to toggle source

Define an octet string field in the current subclass of BitStruct, with the given name and length (in bits). Trailing nulls are not considered part of the string. The field is accessed using period-separated hex digits.

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

# File lib/bit-struct/fields.rb, line 47
def hex_octets(name, length, *rest)
  opts = parse_options(rest, name, HexOctetField)
  add_field(name, length, opts)
end
nest(name, *rest, &block) click to toggle source

Define a nested field in the current subclass of BitStruct, with the given name and nested_class. Length is determined from nested_class.

If a class is provided, use it for the Field class (i.e. <=NestedField). If a string is provided, use it for the display_name. If a hash is provided, use it for options.

For example:

class Sub < BitStruct
  unsigned :x,    8
end

class A < BitStruct
  nest    :n,  Sub
end

a = A.new

p a  # ==> #<A n=#<Sub x=0>>

If a block is given, use it to define the nested fields. For example, the following is equivalent to the above example:

class A < BitStruct
  nest :n do
    unsigned :x, 8
  end
end

WARNING: the accessors have COPY semantics, not reference. When you call a reader method to get the nested structure, you get a copy of that data. Expressed in terms of the examples above:

# This fails to set x in a.
a.n.x = 3
p a  # ==> #<A n=#<Sub x=0>>

# This works
n = a.n
n.x = 3
a.n = n
p a  # ==> #<A n=#<Sub x=3>>
# File lib/bit-struct/fields.rb, line 98
def nest(name, *rest, &block)
  nested_class = rest.grep(Class).find {|cl| cl <= BitStruct}
  rest.delete nested_class
  opts = parse_options(rest, name, NestedField)
  nested_class = opts[:nested_class] ||= nested_class

  unless (block and not nested_class) or (nested_class and not block)
    raise ArgumentError,
      "nested field must have either a nested_class option or a block," +
      " but not both"
  end

  unless nested_class
    nested_class = Class.new(BitStruct)
    nested_class.class_eval(&block)
  end

  opts[:default] ||= nested_class.initial_value.dup
  opts[:nested_class] = nested_class
  field = add_field(name, nested_class.bit_length, opts)
  field
end
Also aliased as: struct
octets(name, length, *rest) click to toggle source

Define an octet string field in the current subclass of BitStruct, with the given name and length (in bits). Trailing nulls are not considered part of the string. The field is accessed using period-separated decimal digits.

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

# File lib/bit-struct/fields.rb, line 132
def octets(name, length, *rest)
  opts = parse_options(rest, name, OctetField)
  add_field(name, length, opts)
end
pad(name, length, *rest) click to toggle source

Define a padding field in the current subclass of BitStruct, with the given name and length (in bits).

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

# File lib/bit-struct/fields.rb, line 145
def pad(name, length, *rest)
  opts = parse_options(rest, name, PadField)
  add_field(name, length, opts)
end
Also aliased as: padding
padding(name, length, *rest)
Alias for: pad
rest(name, *ary) click to toggle source

Define accessors for a variable length substring from the end of the defined fields to the end of the BitStruct. The rest may behave as a String or as some other String or BitStruct subclass.

This does not add a field, which is useful because a superclass can have a rest method which accesses subclass data. In particular, rest does not affect the round_byte_length class method. Of course, any data in rest does add to the length of the BitStruct, calculated as a string. Also, rest is not inherited.

The ary argument(s) work as follows:

If a class is provided, use it for the Field class (String by default). If a string is provided, use it for the display_name (name by default). If a hash is provided, use it for options.

Warning: the rest reader method returns a copy of the field, so accessors on that returned value do not affect the original rest field.

# File lib/bit-struct/bit-struct.rb, line 459
def self.rest(name, *ary)
  if @rest_field
    raise ArgumentError, "Duplicate rest field: #{name.inspect}."
  end

  opts = parse_options(ary, name, String)
  offset = round_byte_length
  byte_range = offset..-1
  class_eval do
    field_class = opts[:field_class]
    define_method name do ||
      field_class.new(self[byte_range])
    end

    define_method "#{name}=" do |val|
      self[byte_range] = val
    end

    @rest_field = Field.new(offset, -1, name, {
      :display_name => opts[:display_name],
      :rest_class => field_class
    })
  end
end
rest_field() click to toggle source

Not included with the other fields, but accessible separately.

# File lib/bit-struct/bit-struct.rb, line 485
def self.rest_field; @rest_field; end
signed(name, length, *rest) click to toggle source

Define a signed integer field in the current subclass of BitStruct, with the given name and length (in bits).

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

SignedField adds the :fixed => divisor option, which specifies that the internally stored value is interpreted as a fixed point real number with the specified divisor.

The :endian => :native option overrides the default of :network byte ordering, in favor of native byte ordering. Also permitted are :big (same as :network) and :little.

# File lib/bit-struct/fields.rb, line 168
def signed name, length, *rest
  opts = parse_options(rest, name, SignedField)
  add_field(name, length, opts)
end
string(name, length, *rest)
Alias for: char
struct(name, *rest, &block)
Alias for: nest
text(name, length, *rest) click to toggle source

Define a printable text string field in the current subclass of BitStruct, with the given name and length (in bits). Trailing nulls are not considered part of the string.

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

Note that the accessors have COPY semantics, not reference.

# File lib/bit-struct/fields.rb, line 184
def text(name, length, *rest)
  opts = parse_options(rest, name, TextField)
  add_field(name, length, opts)
end
unsigned(name, length, *rest) click to toggle source

Define a unsigned integer field in the current subclass of BitStruct, with the given name and length (in bits).

If a class is provided, use it for the Field class. If a string is provided, use it for the display_name. If a hash is provided, use it for options.

UnsignedField adds the :fixed => divisor option, which specifies that the internally stored value is interpreted as a fixed point real number with the specified divisor.

The :endian => :native option overrides the default of :network byte ordering, in favor of native byte ordering. Also permitted are :big (same as :network) and :little.

# File lib/bit-struct/fields.rb, line 206
def unsigned name, length, *rest
  opts = parse_options(rest, name, UnsignedField)
  add_field(name, length, opts)
end
vector(name, *rest, &block) click to toggle source

Define a vector field in the current subclass of BitStruct, with the given name.

If a class is provided, use it for the Vector class, otherwise the block must define the entry fields. The two forms looks like this:

class Vec < BitStruct::Vector
  # these declarations apply to *each* entry in the vector:
  unsigned :x,  16
  signed   :y,  32
end

class Packet < BitStruct
  # Using the Vec class defined above
  vector  :v, Vec, "a vector", :length => 5

  # equivalently, using an anonymous subclass of BitStruct::Vector
  vector :v2, "a vector", :length => 5 do
    unsigned :x,  16
    signed   :y,  32
  end
end

If a string is provided, use it for the display_name. If a hash is provided, use it for options. If a number is provided, use it for length (equivalent to using the :length option).

WARNING: the accessors have COPY semantics, not reference. When you call a reader method to get the vector structure, you get a copy of that data.

For example, to modify the numeric fields in a Packet as defined above:

pkt = Packet.new
  vec = pkt.v
    entry = vec[2]
      entry.x = 123
      entry.y = -456
    vec[2] = entry
  pkt.v = vec
# File lib/bit-struct/fields.rb, line 254
def vector(name, *rest, &block)
  opts = parse_options(rest, name, nil)
  cl = opts[:field_class]
  opts[:field_class] = VectorField

  unless (block and not cl) or (cl and not block)
    raise ArgumentError,
      "vector must have either a class or a block, but not both"
  end

  case
  when cl == nil
    vector_class = Class.new(BitStruct::Vector)
    vector_class.class_eval(&block)

  when cl < BitStruct
    vector_class = Class.new(BitStruct::Vector)
    vector_class.struct_class cl

  when cl < BitStruct::Vector
    vector_class = cl

  else raise ArgumentError, "Bad vector class: #{cl.inspect}"
  end

  vector_class.default_options default_options

  length = opts[:length] || rest.grep(Integer).first
    ## what about :length => :lenfield
  unless length
    raise ArgumentError,
      "Must provide length as argument N or as option :length => N"
  end

  opts[:default] ||= vector_class.new(length) ## nil if variable length
  opts[:vector_class] = vector_class

  bit_length = vector_class.struct_class.round_byte_length * 8 * length

  field = add_field(name, bit_length, opts)
  field
end

Public Instance Methods

encode_with(coder) click to toggle source
# File lib/bit-struct/yaml.rb, line 31
def encode_with coder
  yaml_fields = fields.select {|field| field.inspectable?}
  props = yaml_fields.map {|f| f.name.to_s}
  if (rest_field = self.class.rest_field)
    props << rest_field.name.to_s
  end
  props.each do |prop|
    coder[prop] = send(prop)
  end
end
init_with(coder) click to toggle source
# File lib/bit-struct/yaml.rb, line 42
def init_with coder
  self << self.class.initial_value
  coder.map.each do |k, v|
    send("#{k}=", v)
  end
end
to_yaml( opts = {} ) click to toggle source

Return YAML representation of the BitStruct.

# File lib/bit-struct/yaml.rb, line 62
def to_yaml( opts = {} )
  YAML::quick_emit( object_id, opts ) do |out|
    out.map( taguri, to_yaml_style ) do |map|
      to_yaml_properties.each do |m|
        map.add( m, send( m ) )
      end
    end
  end
end

initialization and conversion methods

↑ top

Constants

DEFAULT_TO_H_OPTS

Public Class Methods

initial_value() { |the initial value| ... } click to toggle source

The unique “prototype” object from which new instances are copied. The fields of this instance can be modified in the class definition to set default values for the fields in that class. (Otherwise, defaults defined by the fields themselves are used.) A copy of this object is inherited in subclasses, which they may override using defaults and by writing to the ::initial_value object itself.

If called with a block, yield the initial value object before returning it. Useful for customization within a class definition.

# File lib/bit-struct/bit-struct.rb, line 334
def initial_value   # :yields: the initial value
  unless @initial_value
    iv = defined?(superclass.initial_value) ?
      superclass.initial_value.dup : ""
    if iv.length < round_byte_length
      iv << "\0" * (round_byte_length - iv.length)
    end

    @initial_value = "" # Serves as initval while the real initval is inited
    @initial_value = new(iv)
    @closed = false # only creating the first _real_ instance closes.

    fields.each do |field|
      @initial_value.send("#{field.name}=", field.default) if field.default
    end
  end
  yield @initial_value if block_given?
  @initial_value
end
join(*structs) click to toggle source

Join the given structs (array or multiple args) as a string. Actually, the inherited String#+ instance method is the same, as is using Array#join.

# File lib/bit-struct/bit-struct.rb, line 367
def join(*structs)
  structs.flatten.map {|struct| struct.to_s}.join("")
end
new(value = nil) { |instance| ... } click to toggle source

Initialize the string with the given string or bitstruct, or with a hash of field=>value pairs, or with the defaults for the BitStruct subclass, or with an IO or other object with a read method. Fields can be strings or symbols. Finally, if a block is given, yield the instance for modification using accessors.

# File lib/bit-struct/bit-struct.rb, line 245
def initialize(value = nil)   # :yields: instance
  self << self.class.initial_value

  case value
  when Hash
    value.each do |k, v|
      send "#{k}=", v
    end

  when nil

  else
    if value.respond_to?(:read)
      value = value.read(self.class.round_byte_length)
    end

    self[0, value.length] = value
  end

  self.class.closed!
  yield self if block_given?
end
parse(data, *classes) click to toggle source

Take data (a string or BitStruct) and parse it into instances of the classes, returning them in an array. The classes can be given as an array or a separate arguments. (For parsing a string into a single BitStruct instance, just use the new method with the string as an arg.)

# File lib/bit-struct/bit-struct.rb, line 358
def parse(data, *classes)
  classes.flatten.map do |c|
    c.new(data.slice!(0...c.round_byte_length))
  end
end

Public Instance Methods

[](*args) click to toggle source
Calls superclass method
# File lib/bit-struct/bit-struct.rb, line 306
def [](*args)
  if args.size == 1 and args[0].kind_of?(Integer)
    super.ord
  else
    super
  end
end
[]=(*args) click to toggle source
Calls superclass method
# File lib/bit-struct/bit-struct.rb, line 314
def []=(*args)
  if args.size == 2 and (i=args[0]).kind_of?(Integer)
    super(i, args[1].chr)
  else
    super
  end
end
to_a(include_rest = true) click to toggle source

Returns an array of values of the fields of the BitStruct. By default, include the rest field.

# File lib/bit-struct/bit-struct.rb, line 293
def to_a(include_rest = true)
  ary =
    fields.map do |f|
      send(f.name)
    end

  if include_rest and (rest_field = self.class.rest_field)
    ary << send(rest_field.name)
  end
  ary
end
to_h(opts = DEFAULT_TO_H_OPTS) click to toggle source

Returns a hash of {name=>value,…} for each field. By default, include the rest field. Keys are symbols derived from field names using to_sym, unless <tt>opts<tt> is set to some other method name.

# File lib/bit-struct/bit-struct.rb, line 277
def to_h(opts = DEFAULT_TO_H_OPTS)
  converter = opts[:convert_keys] || :to_sym

  fields_for_to_h = fields
  if opts[:include_rest] and (rest_field = self.class.rest_field)
    fields_for_to_h += [rest_field]
  end

  fields_for_to_h.inject({}) do |h,f|
    h[f.name.send(converter)] = send(f.name)
    h
  end
end

inspection methods

↑ top

Constants

DEFAULT_INSPECT_OPTS
DETAILED_INSPECT_OPTS

Public Instance Methods

inspect(opts = DEFAULT_INSPECT_OPTS)
inspect_detailed() click to toggle source

A more visually appealing inspect method that puts each field/value on a separate line. Very useful when output is scrolling by on a screen.

(This is actually a convenience method to call inspect with the DETAILED_INSPECT_OPTS opts.)

# File lib/bit-struct/bit-struct.rb, line 431
def inspect_detailed
  inspect(DETAILED_INSPECT_OPTS)
end
inspect_with_options(opts = DEFAULT_INSPECT_OPTS) click to toggle source

A standard inspect method which does not add newlines.

# File lib/bit-struct/bit-struct.rb, line 400
def inspect_with_options(opts = DEFAULT_INSPECT_OPTS)
  field_format = opts[:field_format]
  field_name_meth = opts[:field_name_meth]

  fields_for_inspect = fields.select {|field| field.inspectable?}
  if opts[:include_rest] and (rest_field = self.class.rest_field)
    fields_for_inspect << rest_field
  end

  ary = fields_for_inspect.map do |field|
    field_format %
     [field.send(field_name_meth),
      field.inspect_in_object(self, opts)]
  end

  body = ary.join(opts[:separator])

  if opts[:include_class]
    opts[:format] % [self.class, body]
  else
    opts[:simple_format] % body
  end
end
Also aliased as: inspect

metadata inspection methods

↑ top

Constants

DESCRIBE_FORMAT

Default format for describe. Fields are byte, type, name, size, and description.

Public Class Methods

describe(fmt = nil, opts = {}) { |desc| ... } click to toggle source

Textually describe the fields of this class of BitStructs. Returns a printable table (array of line strings), based on fmt, which defaults to describe_format, which defaults to DESCRIBE_FORMAT.

# File lib/bit-struct/bit-struct.rb, line 189
def describe(fmt = nil, opts = {})
  if fmt.kind_of? Hash
    opts = fmt; fmt = nil
  end

  if block_given?
    fields.each do |field|
      field.describe(opts) do |desc|
        yield desc
      end
    end
    nil

  else
    fmt ||= describe_format

    result = []

    unless opts[:omit_header]
      result << fmt % ["byte", "type", "name", "size", "description"]
      result << "-"*70
    end

    fields.each do |field|
      field.describe(opts) do |desc|
        result << fmt % desc
      end
    end

    unless opts[:omit_footer]
      result << @note if @note
    end

    result
  end
end
describe_format() click to toggle source

Can be overridden to use a different format.

# File lib/bit-struct/bit-struct.rb, line 182
def describe_format
  DESCRIBE_FORMAT
end
note(*str) click to toggle source

Subclasses can use this to append a string (or several) to the describe output. Notes are not cumulative with inheritance. When used with no arguments simply returns the note string

# File lib/bit-struct/bit-struct.rb, line 229
def note(*str)
  @note = str unless str.empty?
  @note
end