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
33 def inherited cl
34   cl.instance_eval do
35     @initial_value = nil
36     @closed = nil
37     @rest_field = nil
38     @note = nil
39   end
40 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
 61 def add_field(name, length, opts = {})
 62   round_byte_length ## just to make sure this has been calculated
 63   ## before adding anything
 64 
 65   name = name.to_sym
 66 
 67   if @closed
 68     raise ClosedClassError, "Cannot add field #{name}: " +
 69       "The definition of the #{self.inspect} BitStruct class is closed."
 70   end
 71 
 72   if fields.find {|f|f.name == name}
 73     raise FieldNameError, "Field #{name} is already defined as a field."
 74   end
 75 
 76   if instance_methods(true).find {|m| m == name}
 77     if opts[:allow_method_conflict] || opts["allow_method_conflict"]
 78       warn "Field #{name} is already defined as a method."
 79     else
 80       raise FieldNameError,"Field #{name} is already defined as a method."
 81     end
 82   end
 83 
 84   field_class = opts[:field_class]
 85 
 86   prev = fields[-1] || NULL_FIELD
 87   offset = prev.offset + prev.length
 88   field = field_class.new(offset, length, name, opts)
 89   field.add_accessors_to(self)
 90   fields << field
 91   own_fields << field
 92   @bit_length += field.length
 93   @round_byte_length = (bit_length/8.0).ceil
 94 
 95   if @initial_value
 96     diff = @round_byte_length - @initial_value.length
 97     if diff > 0
 98       @initial_value << "\0" * diff
 99     end
100   end
101 
102   field
103 end
bit_length() click to toggle source

Length, in bits, of this object.

    # File lib/bit-struct/bit-struct.rb
130 def bit_length
131   @bit_length ||= fields.inject(0) {|a, f| a + f.length}
132 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
121 def default_options h = nil
122   @default_options ||= superclass.default_options.dup
123   if h
124     @default_options.merge! h
125   end
126   @default_options
127 end
field_by_name(name) click to toggle source
    # File lib/bit-struct/bit-struct.rb
143 def field_by_name name
144   @field_by_name ||= {}
145   field = @field_by_name[name]
146   unless field
147     field = fields.find {|f| f.name == name}
148     @field_by_name[name] = field if field
149   end
150   field
151 end
fields() click to toggle source

Return the list of fields for this class.

   # File lib/bit-struct/bit-struct.rb
50 def fields
51   @fields ||= self == BitStruct ? [] : superclass.fields.dup
52 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
56 def own_fields
57   @own_fields ||= []
58 end
round_byte_length() click to toggle source

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

    # File lib/bit-struct/bit-struct.rb
135 def round_byte_length
136   @round_byte_length ||= (bit_length/8.0).ceil
137 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
165 def field_by_name name
166   self.class.field_by_name name
167 end
fields() click to toggle source

Return the list of fields for this class.

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

Return the rest field for this class.

    # File lib/bit-struct/bit-struct.rb
160 def rest_field
161   self.class.rest_field
162 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
13 def char(name, length, *rest)
14   opts = parse_options(rest, name, CharField)
15   add_field(name, length, opts)
16 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
32 def float name, length, *rest
33   opts = parse_options(rest, name, FloatField)
34   add_field(name, length, opts)
35 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
47 def hex_octets(name, length, *rest)
48   opts = parse_options(rest, name, HexOctetField)
49   add_field(name, length, opts)
50 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
 98 def nest(name, *rest, &block)
 99   nested_class = rest.grep(Class).find {|cl| cl <= BitStruct}
100   rest.delete nested_class
101   opts = parse_options(rest, name, NestedField)
102   nested_class = opts[:nested_class] ||= nested_class
103 
104   unless (block and not nested_class) or (nested_class and not block)
105     raise ArgumentError,
106       "nested field must have either a nested_class option or a block," +
107       " but not both"
108   end
109 
110   unless nested_class
111     nested_class = Class.new(BitStruct)
112     nested_class.class_eval(&block)
113   end
114 
115   opts[:default] ||= nested_class.initial_value.dup
116   opts[:nested_class] = nested_class
117   field = add_field(name, nested_class.bit_length, opts)
118   field
119 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
132 def octets(name, length, *rest)
133   opts = parse_options(rest, name, OctetField)
134   add_field(name, length, opts)
135 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
145 def pad(name, length, *rest)
146   opts = parse_options(rest, name, PadField)
147   add_field(name, length, opts)
148 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
459 def self.rest(name, *ary)
460   if @rest_field
461     raise ArgumentError, "Duplicate rest field: #{name.inspect}."
462   end
463 
464   opts = parse_options(ary, name, String)
465   offset = round_byte_length
466   byte_range = offset..-1
467   class_eval do
468     field_class = opts[:field_class]
469     define_method name do ||
470       field_class.new(self[byte_range])
471     end
472 
473     define_method "#{name}=" do |val|
474       self[byte_range] = val
475     end
476 
477     @rest_field = Field.new(offset, -1, name, {
478       :display_name => opts[:display_name],
479       :rest_class => field_class
480     })
481   end
482 end
rest_field() click to toggle source

Not included with the other fields, but accessible separately.

    # File lib/bit-struct/bit-struct.rb
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
168 def signed name, length, *rest
169   opts = parse_options(rest, name, SignedField)
170   add_field(name, length, opts)
171 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
184 def text(name, length, *rest)
185   opts = parse_options(rest, name, TextField)
186   add_field(name, length, opts)
187 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
206 def unsigned name, length, *rest
207   opts = parse_options(rest, name, UnsignedField)
208   add_field(name, length, opts)
209 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
254 def vector(name, *rest, &block)
255   opts = parse_options(rest, name, nil)
256   cl = opts[:field_class]
257   opts[:field_class] = VectorField
258 
259   unless (block and not cl) or (cl and not block)
260     raise ArgumentError,
261       "vector must have either a class or a block, but not both"
262   end
263 
264   case
265   when cl == nil
266     vector_class = Class.new(BitStruct::Vector)
267     vector_class.class_eval(&block)
268 
269   when cl < BitStruct
270     vector_class = Class.new(BitStruct::Vector)
271     vector_class.struct_class cl
272 
273   when cl < BitStruct::Vector
274     vector_class = cl
275 
276   else raise ArgumentError, "Bad vector class: #{cl.inspect}"
277   end
278 
279   vector_class.default_options default_options
280 
281   length = opts[:length] || rest.grep(Integer).first
282     ## what about :length => :lenfield
283   unless length
284     raise ArgumentError,
285       "Must provide length as argument N or as option :length => N"
286   end
287 
288   opts[:default] ||= vector_class.new(length) ## nil if variable length
289   opts[:vector_class] = vector_class
290 
291   bit_length = vector_class.struct_class.round_byte_length * 8 * length
292 
293   field = add_field(name, bit_length, opts)
294   field
295 end

Public Instance Methods

encode_with(coder) click to toggle source
   # File lib/bit-struct/yaml.rb
31 def encode_with coder
32   yaml_fields = fields.select {|field| field.inspectable?}
33   props = yaml_fields.map {|f| f.name.to_s}
34   if (rest_field = self.class.rest_field)
35     props << rest_field.name.to_s
36   end
37   props.each do |prop|
38     coder[prop] = send(prop)
39   end
40 end
init_with(coder) click to toggle source
   # File lib/bit-struct/yaml.rb
42 def init_with coder
43   self << self.class.initial_value
44   coder.map.each do |k, v|
45     send("#{k}=", v)
46   end
47 end
to_yaml( opts = {} ) click to toggle source

Return YAML representation of the BitStruct.

   # File lib/bit-struct/yaml.rb
62 def to_yaml( opts = {} )
63   YAML::quick_emit( object_id, opts ) do |out|
64     out.map( taguri, to_yaml_style ) do |map|
65       to_yaml_properties.each do |m|
66         map.add( m, send( m ) )
67       end
68     end
69   end
70 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
334 def initial_value   # :yields: the initial value
335   unless @initial_value
336     iv = defined?(superclass.initial_value) ?
337       superclass.initial_value.dup : ""
338     if iv.length < round_byte_length
339       iv << "\0" * (round_byte_length - iv.length)
340     end
341 
342     @initial_value = "" # Serves as initval while the real initval is inited
343     @initial_value = new(iv)
344     @closed = false # only creating the first _real_ instance closes.
345 
346     fields.each do |field|
347       @initial_value.send("#{field.name}=", field.default) if field.default
348     end
349   end
350   yield @initial_value if block_given?
351   @initial_value
352 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
367 def join(*structs)
368   structs.flatten.map {|struct| struct.to_s}.join("")
369 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
245 def initialize(value = nil)   # :yields: instance
246   self << self.class.initial_value
247 
248   case value
249   when Hash
250     value.each do |k, v|
251       send "#{k}=", v
252     end
253 
254   when nil
255 
256   else
257     if value.respond_to?(:read)
258       value = value.read(self.class.round_byte_length)
259     end
260 
261     self[0, value.length] = value
262   end
263 
264   self.class.closed!
265   yield self if block_given?
266 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
358 def parse(data, *classes)
359   classes.flatten.map do |c|
360     c.new(data.slice!(0...c.round_byte_length))
361   end
362 end

Public Instance Methods

[](*args) click to toggle source
Calls superclass method
    # File lib/bit-struct/bit-struct.rb
306 def [](*args)
307   if args.size == 1 and args[0].kind_of?(Integer)
308     super.ord
309   else
310     super
311   end
312 end
[]=(*args) click to toggle source
Calls superclass method
    # File lib/bit-struct/bit-struct.rb
314 def []=(*args)
315   if args.size == 2 and (i=args[0]).kind_of?(Integer)
316     super(i, args[1].chr)
317   else
318     super
319   end
320 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
293 def to_a(include_rest = true)
294   ary =
295     fields.map do |f|
296       send(f.name)
297     end
298 
299   if include_rest and (rest_field = self.class.rest_field)
300     ary << send(rest_field.name)
301   end
302   ary
303 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
277 def to_h(opts = DEFAULT_TO_H_OPTS)
278   converter = opts[:convert_keys] || :to_sym
279 
280   fields_for_to_h = fields
281   if opts[:include_rest] and (rest_field = self.class.rest_field)
282     fields_for_to_h += [rest_field]
283   end
284 
285   fields_for_to_h.inject({}) do |h,f|
286     h[f.name.send(converter)] = send(f.name)
287     h
288   end
289 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
431 def inspect_detailed
432   inspect(DETAILED_INSPECT_OPTS)
433 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
400 def inspect_with_options(opts = DEFAULT_INSPECT_OPTS)
401   field_format = opts[:field_format]
402   field_name_meth = opts[:field_name_meth]
403 
404   fields_for_inspect = fields.select {|field| field.inspectable?}
405   if opts[:include_rest] and (rest_field = self.class.rest_field)
406     fields_for_inspect << rest_field
407   end
408 
409   ary = fields_for_inspect.map do |field|
410     field_format %
411      [field.send(field_name_meth),
412       field.inspect_in_object(self, opts)]
413   end
414 
415   body = ary.join(opts[:separator])
416 
417   if opts[:include_class]
418     opts[:format] % [self.class, body]
419   else
420     opts[:simple_format] % body
421   end
422 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
189 def describe(fmt = nil, opts = {})
190   if fmt.kind_of? Hash
191     opts = fmt; fmt = nil
192   end
193 
194   if block_given?
195     fields.each do |field|
196       field.describe(opts) do |desc|
197         yield desc
198       end
199     end
200     nil
201 
202   else
203     fmt ||= describe_format
204 
205     result = []
206 
207     unless opts[:omit_header]
208       result << fmt % ["byte", "type", "name", "size", "description"]
209       result << "-"*70
210     end
211 
212     fields.each do |field|
213       field.describe(opts) do |desc|
214         result << fmt % desc
215       end
216     end
217 
218     unless opts[:omit_footer]
219       result << @note if @note
220     end
221 
222     result
223   end
224 end
describe_format() click to toggle source

Can be overridden to use a different format.

    # File lib/bit-struct/bit-struct.rb
182 def describe_format
183   DESCRIBE_FORMAT
184 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
229 def note(*str)
230   @note = str unless str.empty?
231   @note
232 end