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.
# 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
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 << "\00"" * diff end end field end
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
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
# 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
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
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
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
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
Return the list of fields for this class.
# File lib/bit-struct/bit-struct.rb, line 155 def fields self.class.fields end
Return the rest field for this class.
# File lib/bit-struct/bit-struct.rb, line 160 def rest_field self.class.rest_field end
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
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
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
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
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
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
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
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
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
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
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
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
# 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
# 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
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
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 << "\00"" * (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 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
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
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
# 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
# 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
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
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
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
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
Default format for describe. Fields are byte, type, name, size, and description.
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
Can be overridden to use a different format.
# File lib/bit-struct/bit-struct.rb, line 182 def describe_format DESCRIBE_FORMAT end
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