Class: Binstruct
Overview
There are two main classes of methods for a Binstruct, but because of the implementation they are all actually instance methods. The “construction” methods are endian
, bitfield
, substruct
and all the builders for field subtypes. Each class in the Fields module has a builder, so UnsignedField is just unsigned. Those methods are created at runtime, so they can’t be seen by rdoc, but they all look like:
unsigned, buffer_name, :accessor_sym, length_in_bits, "Description"
Inside a parse block, the buffer name used for the field builders should match the block parameter, unless you are manually messing with the buffer. The same applies to fields created inside the blocks for substructs and bitfields.
The other methods are ‘proper’ instance methods, to work with the structure once it is created and filled with data. Each field creates a getter and setter method matching its symbol, and also a direct access via [:field_sym] that returns the field itself, which gives you access to the field’s own instance methods like set_raw
(see Fields).
Direct Known Subclasses
Defined Under Namespace
Classes: Bitfield
Constant Summary collapse
- VERSION =
"1.0.0"
Class Attribute Summary collapse
-
.init_block ⇒ Object
readonly
Returns the value of attribute init_block.
Instance Attribute Summary collapse
-
#endian(sym) ⇒ Object
Set the endianness for the whole structure.
-
#fields ⇒ Object
Returns the value of attribute fields.
-
#groups ⇒ Object
readonly
Returns the value of attribute groups.
Class Method Summary collapse
-
.parse(&blk) ⇒ Object
There are two ways to create a Binstruct subclass, one is by calling parse inside the structure definition: class Foo < Binstruct parse {|buffer_as_binary| #definitions here } end and the other is by just supplying a block to new: quick_struct=Binstruct.new {|b| string, b, :foo, 32, “Some String”} Otherwise,
Binstruct.new
will just create a blank structure (this can be useful if you want to fill in the fields at runtime).
Instance Method Summary collapse
-
#[](sym) ⇒ Object
return an object, specified by symbol.
-
#bitfield(bitbuf, len, &blk) ⇒ Object
In little endian structures, byte-swaps 16, 32 or 64 bits and presents them for carving into fields.
-
#deep_each(&blk) ⇒ Object
yield all fields in the structure, entering nested substructs as necessary.
-
#each(&blk) ⇒ Object
yield each object to the block.
-
#flatten ⇒ Object
Flattens all fields in nested structures into an array, preserving order.
-
#group(groupname, *fieldsyms) ⇒ Object
Groups a list of fields under
:groupname
. -
#initialize(buffer = nil, &blk) ⇒ Binstruct
constructor
————END CONSTRUCTION——————.
-
#inspect ⇒ Object
Returns an array of terse field descriptions which show the index, field class, name and length in bits, plus a hexdumped snippet of the contents.
-
#length ⇒ Object
Packed length in bytes.
-
#replace(oldthing, newthing) ⇒ Object
Searches a Binstruct, recursing through nested structures as neccessary, and replaces a given object with a new object.
-
#substruct(strbuf, name, len, klass) ⇒ Object
Creates a nested structure, and a direct accesor for it that returns the structure itself, so accessors like
main.sub1.sub2.some_val
are possible. -
#to_s ⇒ Object
pack current struct as a string - for Fields, it will use the bitstring, for anything else (including Bitfields and Binstructs) it will use
to_s.unpack('B*')
.
Constructor Details
#initialize(buffer = nil, &blk) ⇒ Binstruct
————END CONSTRUCTION——————
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/binstruct.rb', line 128 def initialize(buffer=nil, &blk) @fields=[] @hash_references={} @endian_flip_hack=false @groups=Hash.new {|h,k| h[k]=[]} buffer||="" @bitbuf=buffer.unpack('B*').join if block_given? instance_exec(@bitbuf, &blk) elsif self.class.init_block instance_exec(@bitbuf, &self.class.init_block) else # do nothing, user probably just wants a blank struct to manually add fields. end endian :big unless @endian @groups.each {|group, contents| unless contents.flatten.all? {|sym| @hash_references.keys.any? {|othersym| othersym==sym}} raise RuntimeError, "Binstruct: Construction: group #{group} contains invalid field name(s)" end } # This is not ideal for structures that aren't byte aligned, but raising an exception # would be less flexible. buffer.replace @bitbuf.scan(/.{8}/).map {|e| e.to_i(2).chr}.join unless buffer.nil? end |
Class Attribute Details
.init_block ⇒ Object (readonly)
Returns the value of attribute init_block.
108 109 110 |
# File 'lib/binstruct.rb', line 108 def init_block @init_block end |
Instance Attribute Details
#endian(sym) ⇒ Object
Set the endianness for the whole structure. Default is :big
, options are :big
or :little
. Fields created after this method is invoked in a construction block will be created with the new endianness. You can also set the endianness after construction with somestruct.endian=:little
.
34 35 36 |
# File 'lib/binstruct.rb', line 34 def endian @endian end |
#fields ⇒ Object
Returns the value of attribute fields.
26 27 28 |
# File 'lib/binstruct.rb', line 26 def fields @fields end |
#groups ⇒ Object (readonly)
Returns the value of attribute groups.
25 26 27 |
# File 'lib/binstruct.rb', line 25 def groups @groups end |
Class Method Details
.parse(&blk) ⇒ Object
There are two ways to create a Binstruct subclass, one is by calling parse inside the structure definition:
class Foo < Binstruct
parse {|buffer_as_binary|
#definitions here
}
end
and the other is by just supplying a block to new:
quick_struct=Binstruct.new {|b| string, b, :foo, 32, "Some String"}
Otherwise, Binstruct.new
will just create a blank structure (this can be useful if you want to fill in the fields at runtime).
122 123 124 |
# File 'lib/binstruct.rb', line 122 def self.parse( &blk ) @init_block=blk end |
Instance Method Details
#[](sym) ⇒ Object
return an object, specified by symbol. May be a field or a substruct. not designed for bitfields, since they’re supposed to be invisible containers.
158 159 160 |
# File 'lib/binstruct.rb', line 158 def []( sym ) @hash_references[sym] end |
#bitfield(bitbuf, len, &blk) ⇒ Object
In little endian structures, byte-swaps 16, 32 or 64 bits and presents them for carving into fields. Only really neccessary for non-byte aligned field sets in little endian structures. The bitfield itself is invisible but the fields are created with the usual accessors.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/binstruct.rb', line 47 def bitfield(bitbuf, len, &blk) if @endian==:little unless len==16||len==32||len==64 raise RuntimeError, "Binstruct: Bitfield: Don't know how to endian swap #{len} bits. :(" end instr=bitbuf.slice!(0,len).scan(/.{8}/).reverse.join else instr=bitbuf.slice!(0,len) end new=Bitfield.new([instr].pack('B*'), &blk) if @endian==:little # This is so we know to flip the bytes back in #to_s new.instance_variable_set :@endian_flip_hack, true end @fields << new # Add direct references and accessor methods to the containing Binstruct new.fields.each {|f| unless f.is_a? Fields::Field raise RuntimeError, "Binstruct: Construction: Illegal content #{f.class} in bitfield - use only Fields" end @hash_references[f.name]=f f.name do f.get_value end (f.name.to_s + '=').to_sym do |val| f.set_value(val) end } end |
#deep_each(&blk) ⇒ Object
yield all fields in the structure, entering nested substructs as necessary
179 180 181 182 183 184 185 186 187 |
# File 'lib/binstruct.rb', line 179 def deep_each( &blk ) #:yields: a Field @fields.each {|atom| if atom.is_a? Binstruct atom.deep_each &blk unless atom.fields.empty? else yield atom end } end |
#each(&blk) ⇒ Object
yield each object to the block. This is a little messy, because substructs are not Fields::Field types. For Bitfields, just silently yield each component, not the container field. The upshot of all this is that the caller needs to be prepared for a Field or a Binstruct in the block. This is the ‘shallow’ each.
167 168 169 170 171 172 173 174 175 176 |
# File 'lib/binstruct.rb', line 167 def each( &blk ) #:yields: a Field or a Bitstruct @fields.each {|atom| if atom.is_a? Bitfield atom.fields.each {|f| yield f} else yield atom end } end |
#flatten ⇒ Object
Flattens all fields in nested structures into an array, preserving order. In some cases (eg little endian structures with bitfields) this will mean that struct.flatten.join will not be the same as struct.to_s.
210 211 212 213 214 |
# File 'lib/binstruct.rb', line 210 def flatten a=[] self.deep_each {|f| a << f} a end |
#group(groupname, *fieldsyms) ⇒ Object
Groups a list of fields under :groupname
. Designed for use in Metafuzz. somestruct.groups
will return the hash of {:group_sym=>[field1, field2...]}
103 104 105 |
# File 'lib/binstruct.rb', line 103 def group( groupname, *fieldsyms ) @groups[groupname] << fieldsyms end |
#inspect ⇒ Object
Returns an array of terse field descriptions which show the index, field class, name and length in bits, plus a hexdumped snippet of the contents.
246 247 248 249 |
# File 'lib/binstruct.rb', line 246 def inspect # This could possibly be more readable... self.flatten.map {|field| "<IDX:#{self.flatten.index(field)}><#{field.class.to_s.match(/::(.+)Field/)[1]}><#{field.name}><#{field.length}><#{field.to_s[0..12].each_byte.to_a.map {|b| "%.2x" % b}.join(' ') + (field.to_s.length>12?"...":"")}>"} end |
#length ⇒ Object
Packed length in bytes.
240 241 242 |
# File 'lib/binstruct.rb', line 240 def length self.to_s.length end |
#replace(oldthing, newthing) ⇒ Object
Searches a Binstruct, recursing through nested structures as neccessary, and replaces a given object with a new object. Note that this replaces the object that ==oldthing, so a reference to it is needed first.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/binstruct.rb', line 192 def replace(oldthing, newthing) k,v=@hash_references.select {|k,v| v==oldthing}.flatten @hash_references[k]=newthing @fields.map! {|atom| if atom==oldthing newthing else if atom.is_a? Binstruct atom.replace(oldthing,newthing) end atom end } end |
#substruct(strbuf, name, len, klass) ⇒ Object
Creates a nested structure, and a direct accesor for it that returns the structure itself, so accessors like main.sub1.sub2.some_val
are possible. When iterating over the Binstruct contents, see #each
, which will pass substructs to the block and #deep_each
which recursively enters substructs and passes only Fields.
79 80 81 82 83 84 85 86 |
# File 'lib/binstruct.rb', line 79 def substruct(strbuf, name, len, klass) new=klass.new(strbuf) @fields << new @hash_references[name]=new name do new end # More informative than the NoMethodError they would normally get. (name.to_s + '=').to_sym do raise NoMethodError, "Binstruct: Illegal call of '=' on a substruct." end end |
#to_s ⇒ Object
pack current struct as a string - for Fields, it will use the bitstring, for anything else (including Bitfields and Binstructs) it will use to_s.unpack('B*')
. Via a filthy internal hack, bitfields get byte-swapped back in here. Finally, once the bitstring is assembled, it is right-padded with 0 and packed as a string.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/binstruct.rb', line 222 def to_s bits=@fields.inject("") {|str,field| field.kind_of?(Fields::Field) ? str << field.bitstring : str << field.to_s.unpack('B*').join } unless bits.length % 8 == 0 #puts "Warning, structure not byte aligned, right padding with 0" end return "" if bits.empty? bytearray=bits.scan(/.{1,8}/) # If not byte aligned, right pad with 0 bytearray.last << '0'*(8-bytearray.last.length) if bytearray.last.length < 8 # This only happens for Binstructs that have the endian_flip_hack ivar # set, so only inside the to_s of a Bitfield when little endian. bytearray=bytearray.reverse if @endian_flip_hack bytearray.map {|byte| "" << byte.to_i(2)}.join end |