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
# 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
↑ topPublic Class Methods
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
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
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
# 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
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
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
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
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
Return the list of fields for this class.
# File lib/bit-struct/bit-struct.rb 155 def fields 156 self.class.fields 157 end
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
↑ topPublic Class Methods
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
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
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
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
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
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
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
Not included with the other fields, but accessible separately.
# File lib/bit-struct/bit-struct.rb 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 168 def signed name, length, *rest 169 opts = parse_options(rest, name, SignedField) 170 add_field(name, length, opts) 171 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 184 def text(name, length, *rest) 185 opts = parse_options(rest, name, TextField) 186 add_field(name, length, opts) 187 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 206 def unsigned name, length, *rest 207 opts = parse_options(rest, name, UnsignedField) 208 add_field(name, length, opts) 209 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 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
# 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
# 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
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
↑ topConstants
- DEFAULT_TO_H_OPTS
Public Class Methods
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 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
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
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
# 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
# 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
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
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
↑ topConstants
- DEFAULT_INSPECT_OPTS
- DETAILED_INSPECT_OPTS
Public Instance Methods
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
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
metadata inspection methods
↑ topConstants
- DESCRIBE_FORMAT
Default format for describe. Fields are byte, type, name, size, and description.
Public Class Methods
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
Can be overridden to use a different format.
# File lib/bit-struct/bit-struct.rb 182 def describe_format 183 DESCRIBE_FORMAT 184 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 229 def note(*str) 230 @note = str unless str.empty? 231 @note 232 end