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.

Contents
Methods
Classes and Modules
Class BitStruct::ClosedClassError
Class BitStruct::Field
Class BitStruct::FieldNameError
Constants
VERSION = "0.13.6"
NULL_FIELD = Field.new(0, 0, :null, :display_name => "null field")
Public Class methods
inherited(cl)
# File lib/bit-struct/bit-struct.rb, line 122
    def inherited cl
      cl.instance_eval do
        @initial_value = nil
        @closed = nil
        @rest_field = nil
        @note = nil
      end
    end

For introspection and metaprogramming.

Public Class methods
add_field(name, length, opts = {})

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

# File lib/bit-struct/bit-struct.rb, line 150
    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()

Length, in bits, of this object.

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

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 210
    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)
# File lib/bit-struct/bit-struct.rb, line 232
    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()

Return the list of fields for this class.

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

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

# File lib/bit-struct/bit-struct.rb, line 145
    def own_fields
      @own_fields ||= []
    end
round_byte_length()

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

# File lib/bit-struct/bit-struct.rb, line 224
    def round_byte_length
      @round_byte_length ||= (bit_length/8.0).ceil
    end
Public Instance methods
field_by_name(name)

Return the field with the given name.

# File lib/bit-struct/bit-struct.rb, line 254
  def field_by_name name
    self.class.field_by_name name
  end
fields()

Return the list of fields for this class.

# File lib/bit-struct/bit-struct.rb, line 244
  def fields
    self.class.fields
  end
rest_field()

Return the rest field for this class.

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

Methods to textually describe the format of a BitStruct subclass.

Constants
DESCRIBE_FORMAT = "%8s: %-12s %-14s[%4s] %s"
  Default format for describe. Fields are byte, type, name, size, and description.
Public Class methods
describe(fmt = nil, opts = {}) {|desc| ...}

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 278
    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()

Can be overridden to use a different format.

# File lib/bit-struct/bit-struct.rb, line 271
    def describe_format
      DESCRIBE_FORMAT
    end
note(*str)

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 318
    def note(*str)
      @note = str unless str.empty?
      @note
    end
Constants
DEFAULT_TO_H_OPTS = { :convert_keys => :to_sym, :include_rest => true
Public Class methods
initial_value( {|the initial value| ...}

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 424
    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)

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 457
    def join(*structs)
      structs.flatten.map {|struct| struct.to_s}.join("")
    end
new(value = nil) {|instance| ...}

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 334
  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)

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 448
    def parse(data, *classes)
      classes.flatten.map do |c|
        c.new(data.slice!(0...c.round_byte_length))
      end
    end
Public Instance methods
[](*args)
# File lib/bit-struct/bit-struct.rb, line 396
    def [](*args)
      if args.size == 1 and args[0].kind_of?(Fixnum)
        super.ord
      else
        super
      end
    end
[]=(*args)
# File lib/bit-struct/bit-struct.rb, line 404
    def []=(*args)
      if args.size == 2 and (i=args[0]).kind_of?(Fixnum)
        super(i, args[1].chr)
      else
        super
      end
    end
to_a(include_rest = true)

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 382
  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)

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[:convert_keys]<\tt> is set to some other method name.

# File lib/bit-struct/bit-struct.rb, line 366
  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
Constants
DEFAULT_INSPECT_OPTS = { :format => "#<%s %s>", :field_format => "%s=%s", :separator => ", ", :field_name_meth => :name, :include_rest => true, :brackets => ["[", "]"], :include_class => true, :simple_format => "<%s>"
DETAILED_INSPECT_OPTS = { :format => "%s:\n%s", :field_format => "%30s = %s", :separator => "\n", :field_name_meth => :display_name, :include_rest => true, :brackets => [nil, "\n"], :include_class => true, :simple_format => "\n%s"
Public Instance methods
inspect(opts = DEFAULT_INSPECT_OPTS)

A standard inspect method which does not add newlines.

# File lib/bit-struct/bit-struct.rb, line 490
  def inspect(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
inspect_detailed()

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 519
  def inspect_detailed
    inspect(DETAILED_INSPECT_OPTS)
  end
Classes and Modules
Class BitStruct::CharField
Class BitStruct::FloatField
Class BitStruct::HexOctetField
Class BitStruct::NestedField
Class BitStruct::OctetField
Class BitStruct::PadField
Class BitStruct::SignedField
Class BitStruct::TextField
Class BitStruct::UnsignedField
Class BitStruct::Vector
Class BitStruct::VectorField
Public Class methods
char(name, length, *rest)

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
float(name, length, *rest)

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)

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)

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
octets(name, length, *rest)

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)

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
rest(name, *ary)

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 547
  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()

Not included with the other fields, but accessible separately.

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

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
text(name, length, *rest)

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)

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)

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
to_yaml( opts = {} )

Return YAML representation of the BitStruct.

# File lib/bit-struct/yaml.rb, line 43
    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