rstruct 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +675 -0
- data/README.rdoc +137 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/rstruct.rb +70 -0
- data/lib/rstruct/base_types.rb +5 -0
- data/lib/rstruct/base_types/container_type.rb +102 -0
- data/lib/rstruct/base_types/packed_type.rb +78 -0
- data/lib/rstruct/base_types/type.rb +55 -0
- data/lib/rstruct/field.rb +22 -0
- data/lib/rstruct/registry.rb +66 -0
- data/lib/rstruct/struct_builder.rb +30 -0
- data/lib/rstruct/structure.rb +59 -0
- data/lib/rstruct/types.rb +44 -0
- data/samples/fatparse.rb +78 -0
- data/spec/registry_behaviors.rb +64 -0
- data/spec/registry_spec.rb +61 -0
- data/spec/rstruct_spec.rb +88 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/structure_spec.rb +297 -0
- data/spec/type_behaviors.rb +158 -0
- metadata +144 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rstruct
|
2
|
+
class Type
|
3
|
+
attr_reader :name, :params
|
4
|
+
|
5
|
+
def initialize(name, params={}, &block)
|
6
|
+
@params = params.dup
|
7
|
+
@name = name.to_sym
|
8
|
+
|
9
|
+
reg = @params.delete(:register)
|
10
|
+
aliases = @params.delete(:alias)
|
11
|
+
regnames = ((aliases)? ([aliases] << @name).flatten : [@name]).uniq.compact
|
12
|
+
|
13
|
+
reg=nil if reg==true
|
14
|
+
register(regnames, reg) unless reg == false
|
15
|
+
|
16
|
+
instance_eval &block if block
|
17
|
+
end
|
18
|
+
|
19
|
+
def register(names=nil, reg=nil)
|
20
|
+
names ||= to_sym
|
21
|
+
reg ||= Registry::DEFAULT_REGISTRY
|
22
|
+
reg.register(self, *names)
|
23
|
+
end
|
24
|
+
|
25
|
+
def groupable?
|
26
|
+
@groupable or false
|
27
|
+
end
|
28
|
+
|
29
|
+
def container?
|
30
|
+
@container or false
|
31
|
+
end
|
32
|
+
|
33
|
+
def claim_value(vals, predecessors=nil)
|
34
|
+
if @claim_cb
|
35
|
+
@claim_cb.call(vals, predecessors)
|
36
|
+
else
|
37
|
+
vals.shift
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def sizeof
|
42
|
+
self.size
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
# sets up a call back for claiming values out of an unpacked
|
47
|
+
# value array
|
48
|
+
def claim(&block)
|
49
|
+
@claim_cb = block
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rstruct
|
2
|
+
class Field
|
3
|
+
attr_reader :name, :typ_name, :typ, :args, :block
|
4
|
+
|
5
|
+
def initialize(name, typ, typ_name, args, block)
|
6
|
+
@name=name
|
7
|
+
@typ=typ
|
8
|
+
@typ_name = typ_name || typ
|
9
|
+
@args=args
|
10
|
+
@block=block
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to?(arg)
|
14
|
+
super(arg) || @typ.respond_to?(arg)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(name, *args, &block)
|
18
|
+
@typ.__send__(name, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Rstruct
|
4
|
+
class TypeConflictError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class InvalidTypeError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class Registry
|
11
|
+
attr_reader :name, :inherits
|
12
|
+
|
13
|
+
def initialize(name, *inherits)
|
14
|
+
@name = name.to_sym
|
15
|
+
@registry = Hash.new()
|
16
|
+
|
17
|
+
@inherits = []
|
18
|
+
if @name != :default
|
19
|
+
@inherits << DEFAULT_REGISTRY
|
20
|
+
end
|
21
|
+
@inherits.concat(inherits).uniq!
|
22
|
+
end
|
23
|
+
|
24
|
+
def typedef(p,t,opts={})
|
25
|
+
if (pt = get(p))
|
26
|
+
set(t, pt)
|
27
|
+
else
|
28
|
+
raise(InvalidTypeError, "unknown type: #{p}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(typ)
|
33
|
+
if t=@registry[typ]
|
34
|
+
return t
|
35
|
+
else
|
36
|
+
@inherits.each do |inc|
|
37
|
+
if t=inc.get(typ)
|
38
|
+
return t
|
39
|
+
end
|
40
|
+
end
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
alias [] get
|
46
|
+
|
47
|
+
def set(n,typ)
|
48
|
+
if n.nil?
|
49
|
+
raise(TypeConflictError, "can't register nil type")
|
50
|
+
elsif v=get(n)
|
51
|
+
raise(TypeConflictError, "type already registered: #{n} => #{v.inspect}" )
|
52
|
+
end
|
53
|
+
@registry[n]=typ
|
54
|
+
end
|
55
|
+
|
56
|
+
alias []= set
|
57
|
+
|
58
|
+
def register(typ, *names)
|
59
|
+
names.each do |n|
|
60
|
+
set(n.to_sym,typ)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
DEFAULT_REGISTRY=new(:default)
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rstruct/registry'
|
2
|
+
require 'rstruct/field'
|
3
|
+
|
4
|
+
module Rstruct
|
5
|
+
|
6
|
+
class StructBuilder
|
7
|
+
attr_reader :__fields, :__registry
|
8
|
+
|
9
|
+
def initialize(registry=nil, &block)
|
10
|
+
@__fields = []
|
11
|
+
@__registry = registry || Registry::DEFAULT_REGISTRY
|
12
|
+
instance_eval &block
|
13
|
+
end
|
14
|
+
|
15
|
+
def field(name, typ, typ_name, *args, &block)
|
16
|
+
@__fields << Field.new(name,typ,typ_name,args,block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(typ_arg, *args, &block)
|
20
|
+
name = args.shift
|
21
|
+
unless typ = @__registry.get(typ_arg)
|
22
|
+
raise(InvalidTypeError, "invalid field type: #{typ_arg}")
|
23
|
+
end
|
24
|
+
|
25
|
+
field(name, typ, typ_arg, *args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rstruct/base_types'
|
2
|
+
require 'rstruct/struct_builder'
|
3
|
+
require 'rstruct/registry'
|
4
|
+
|
5
|
+
module Rstruct
|
6
|
+
class StructError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class Structure < ContainerType
|
10
|
+
attr_reader :fields
|
11
|
+
|
12
|
+
def initialize(name, opts={}, &block)
|
13
|
+
lkupreg = (opts[:fields_from]) # allow a seperate reg for member types
|
14
|
+
builder = opts[:builder] || StructBuilder
|
15
|
+
reg = opts[:register]
|
16
|
+
reg=nil if reg==true
|
17
|
+
|
18
|
+
# pass a nil block to super to ensure we're claiming the caller's
|
19
|
+
super(name, opts, &(nil))
|
20
|
+
|
21
|
+
@fields = builder.new((lkupreg || reg), &block).__fields
|
22
|
+
raise(StructError, "no fields were defined") if @fields.empty?
|
23
|
+
|
24
|
+
# set up our internal struct container class
|
25
|
+
# we are taking the field name 'structure'
|
26
|
+
# to reference ourselves
|
27
|
+
@mystruct = Struct.new(*self.field_names)
|
28
|
+
end
|
29
|
+
|
30
|
+
def instance(values=nil)
|
31
|
+
values ||= {}
|
32
|
+
vals = []
|
33
|
+
self.fields.each do |f|
|
34
|
+
v = values[f.name]
|
35
|
+
vals << (f.typ.respond_to?(:instance) ? f.typ.instance(v) : v)
|
36
|
+
end
|
37
|
+
s = @mystruct.new(*vals).extend(ContainerMixins)
|
38
|
+
s.rstruct_type = self
|
39
|
+
yield(s) if block_given?
|
40
|
+
return s
|
41
|
+
end
|
42
|
+
|
43
|
+
def claim_value(vals, obj=nil)
|
44
|
+
if @claim_cb
|
45
|
+
@claim_cb.call(vals, obj)
|
46
|
+
else
|
47
|
+
# create our struct container
|
48
|
+
s = instance()
|
49
|
+
|
50
|
+
# iterate through the fields assigning values in the
|
51
|
+
# container and pass it along with values to each
|
52
|
+
# field's claim_value method.
|
53
|
+
self.fields.do {|f| s[f.name] = f.typ.claim_value(vals,s) }
|
54
|
+
|
55
|
+
return s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rstruct/base_types'
|
2
|
+
|
3
|
+
module Rstruct
|
4
|
+
Char = PackedType.new(:char, 1, "A", :alias => [:char, :char_t])
|
5
|
+
|
6
|
+
Byte = PackedType.new(:byte, 1, "c", :alias => [:BYTE, :signed_byte])
|
7
|
+
|
8
|
+
Ubyte = PackedType.new(:ubyte, 1, "C", :alias => [:UBYTE, :unsigned_byte])
|
9
|
+
|
10
|
+
Int = PackedType.new(:int, [0].pack("i_").size, "i_", :alias => [:int_t, :bool, :BOOL, :signed_int])
|
11
|
+
|
12
|
+
Uint = PackedType.new(:uint, [0].pack("I_").size, "I_", :alias => [:uint_t, :unsigned_int])
|
13
|
+
|
14
|
+
Short = PackedType.new(:short, [0].pack("s_").size, "s_", :alias => [:short_t, :signed_short])
|
15
|
+
|
16
|
+
Ushort = PackedType.new(:ushort, [0].pack("S_").size, "S_", :alias => [:ushort_t, :unsigned_short])
|
17
|
+
|
18
|
+
Long = PackedType.new(:long, [0].pack("l_").size, "l_", :alias => [:long_t, :signed_long])
|
19
|
+
|
20
|
+
Ulong = PackedType.new(:ulong, [0].pack("L_").size, "L_", :alias => [:ulong_t, :unsigned_long])
|
21
|
+
|
22
|
+
Int16 = PackedType.new(:int16, 2, 's', :alias => [:int16_t, :i16, :i16_t, :signed_int16])
|
23
|
+
|
24
|
+
Uint16 = PackedType.new(:uint16, 2, 'S', :alias => [:uint16_t, :u16, :u16_t, :unsigned_int16])
|
25
|
+
|
26
|
+
Int32 = PackedType.new(:int32, 4, "l", :alias => [:int32_t, :i32, :i32_t, :signed_int32])
|
27
|
+
|
28
|
+
Uint32 = PackedType.new(:uint32, 4, "L", :alias => [:uint32_t, :u32, :u32_t, :unsigned_int32])
|
29
|
+
|
30
|
+
Int64 = PackedType.new(:int64, 8, "q", :alias => [:int64_t, :i64, :i64_t, :signed_int64])
|
31
|
+
|
32
|
+
Uint64 = PackedType.new(:uint64, 8, "Q", :alias => [:uint64_t, :u64, :u64_t, :unsigned_int64])
|
33
|
+
|
34
|
+
Uint16le = PackedType.new(:uint16le, 2, "v", :alias => [:uint16_le, :ul16, :le16])
|
35
|
+
|
36
|
+
Uint32le = PackedType.new(:uint32le, 4, "V", :alias => [:uint32_le, :ul32, :le32])
|
37
|
+
|
38
|
+
Uint16be = PackedType.new(:uint16be, 2, "n", :alias => [:uint16_be, :ub16, :be16])
|
39
|
+
|
40
|
+
Uint32be = PackedType.new(:uint32be, 4, "N", :alias => [:uint32_be, :ub32, :be32])
|
41
|
+
|
42
|
+
Pointer = PackedType.new(:pointer, [0].pack("L_").size , "L_", :alias => :pointer)
|
43
|
+
|
44
|
+
end
|
data/samples/fatparse.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# A basic Rstruct example using Apple's FAT file structure.
|
3
|
+
# To compare the structs to their C counterparts, see:
|
4
|
+
# http://fxr.watson.org/fxr/source/EXTERNAL_HEADERS/mach-o/fat.h?v=xnu-1228
|
5
|
+
|
6
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
|
8
|
+
require 'rstruct'
|
9
|
+
extend Rstruct::ClassMethods
|
10
|
+
|
11
|
+
FAT_MAGIC = 0xcafebabe
|
12
|
+
|
13
|
+
fat_header = struct(:fat_header) {
|
14
|
+
uint32be :magic; # FAT_MAGIC
|
15
|
+
uint32be :nfat_arch; # number of structs that follow
|
16
|
+
}
|
17
|
+
|
18
|
+
typedef :uint32be, :cpu_type_t
|
19
|
+
typedef :uint32be, :cpu_subtype_t
|
20
|
+
|
21
|
+
fat_arch = struct(:fat_arch) {
|
22
|
+
cpu_type_t :cputype # cpu specifier (int)
|
23
|
+
cpu_subtype_t :cpusubtype # machine specifier (int)
|
24
|
+
uint32be :offset # file offset to this object file
|
25
|
+
uint32be :size # size of this object file
|
26
|
+
uint32be :align # alignment as a power of 2
|
27
|
+
}
|
28
|
+
|
29
|
+
typedef :uint32le, :cpu_type_le
|
30
|
+
typedef :uint32le, :cpu_subtype_le
|
31
|
+
|
32
|
+
mach_header = struct(:mach_header) { # little endian this time
|
33
|
+
uint32le :magic # mach magic number identifier
|
34
|
+
cpu_type_le :cputype # cpu specifier
|
35
|
+
cpu_subtype_le :cpusubtype # machine specifier
|
36
|
+
uint32le :filetype # type of file
|
37
|
+
uint32le :ncmds # number of load commands
|
38
|
+
uint32le :sizeofcmds # the size of all the load commands
|
39
|
+
uint32le :flags # flags
|
40
|
+
}
|
41
|
+
|
42
|
+
# a basic helper to produce textual dumps of fields
|
43
|
+
def dump(struct)
|
44
|
+
struct.each_pair.map {|k,v| " #{k} = 0x%0.8x" % v}
|
45
|
+
end
|
46
|
+
|
47
|
+
ARGV.each do |fname|
|
48
|
+
File.open(fname,'r') do |f|
|
49
|
+
|
50
|
+
# Read and dump the FAT header from the file
|
51
|
+
head = fat_header.read(f)
|
52
|
+
unless head.magic == FAT_MAGIC
|
53
|
+
STDERR.puts "Error: #{fname} does not look like a FAT binary"
|
54
|
+
next
|
55
|
+
end
|
56
|
+
puts "File: #{fname}", "FAT header:", dump(head)
|
57
|
+
puts
|
58
|
+
|
59
|
+
# parse the architectures located after the FAT header
|
60
|
+
(head.nfat_arch).times do |i|
|
61
|
+
arch = fat_arch.read(f)
|
62
|
+
puts " Architecture #{i}:"
|
63
|
+
puts " " << dump(arch).join("\n ")
|
64
|
+
puts
|
65
|
+
|
66
|
+
# dump the mach header
|
67
|
+
opos = f.pos # save our file position so we get the next arch
|
68
|
+
f.pos = arch.offset # jump to where the mach header should be
|
69
|
+
mach = mach_header.read(f) # parse a mach_header struct
|
70
|
+
f.pos = opos # return to saved architecture position
|
71
|
+
|
72
|
+
puts " Mach Header:"
|
73
|
+
puts " " << dump(mach).join("\n ")
|
74
|
+
puts
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "a basic Rstruct registry" do
|
3
|
+
it "should have a name" do
|
4
|
+
@registry.name.should == @reg_name
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should have basic types already registered or inherited" do
|
8
|
+
@registry.get(:byte).should == Rstruct::Byte
|
9
|
+
@registry.get(:char).should == Rstruct::Char
|
10
|
+
@registry.get(:int).should == Rstruct::Int
|
11
|
+
@registry.get(:short).should == Rstruct::Short
|
12
|
+
@registry.get(:long).should == Rstruct::Long
|
13
|
+
@registry.get(:uint).should == Rstruct::Uint
|
14
|
+
@registry.get(:ushort).should == Rstruct::Ushort
|
15
|
+
@registry.get(:ulong).should == Rstruct::Ulong
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should allow types to be retrieved with 'get' or '[]'" do
|
19
|
+
(a=@registry[:byte]).should == Rstruct::Byte
|
20
|
+
@registry.get(:byte).should == a
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should allow new types to be defined with 'register' or '[]='" do
|
24
|
+
typ1 = :"create_#{@registry.name}_1"
|
25
|
+
c1 = Class.new(Rstruct::Type)
|
26
|
+
@registry[typ1]= c1
|
27
|
+
@registry[typ1].should == c1
|
28
|
+
|
29
|
+
typ2 = :"create_#{@registry.name}_2"
|
30
|
+
c2 = Class.new(Rstruct::Type)
|
31
|
+
@registry.register(c2, typ2)
|
32
|
+
@registry[typ2].should == c2
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should allow multiple names to be registered at once with 'register'" do
|
36
|
+
typ1 = :"create_#{@registry.name}_3"
|
37
|
+
typ2 = :"create_#{@registry.name}_4"
|
38
|
+
c = Class.new(Rstruct::Type)
|
39
|
+
@registry.register(c, typ1, typ2)
|
40
|
+
@registry[typ1].should == c
|
41
|
+
@registry[typ2].should == c
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise an exception when a type is defined which already exists" do
|
45
|
+
typ = :"create_#{@registry.name}_5"
|
46
|
+
c = Class.new(Rstruct::Type)
|
47
|
+
@registry[typ] = c
|
48
|
+
@registry[typ].should == c
|
49
|
+
c2 = Class.new(Rstruct::Type)
|
50
|
+
lambda{ @registry[typ] = c2 }.should raise_error(Rstruct::TypeConflictError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should allow type aliases to be defined with 'typedef'" do
|
54
|
+
typ = :"typedef_#{@registry.name}_1"
|
55
|
+
@registry.typedef :int, typ
|
56
|
+
@registry[:int].should == @registry[typ]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should raise an exception when invalid types are aliased in 'typedef'" do
|
60
|
+
typ = :"typedef_#{@registry.name}_2"
|
61
|
+
lambda{ @registry.typedef :not_a_type, typ}.should raise_error(Rstruct::InvalidTypeError)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'registry_behaviors'
|
3
|
+
|
4
|
+
describe Rstruct::Registry do
|
5
|
+
before(:all) do
|
6
|
+
@dflt_reg = Rstruct::Registry::DEFAULT_REGISTRY
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have a default registry" do
|
10
|
+
@dflt_reg.should be_a(Rstruct::Registry)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "default registry" do
|
14
|
+
before(:all) do
|
15
|
+
@registry = @dflt_reg
|
16
|
+
@reg_name = :default
|
17
|
+
end
|
18
|
+
|
19
|
+
it_should_behave_like "a basic Rstruct registry"
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context "creating a new registry" do
|
24
|
+
before(:all) do
|
25
|
+
@reg_name = :rspec_test_registry
|
26
|
+
@preg = Rstruct::Registry.new(:parent_reg)
|
27
|
+
@registry = Rstruct::Registry.new(@reg_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should inherit types from the default registry" do
|
31
|
+
@registry[:byte].should == Rstruct::Byte
|
32
|
+
@registry[:int].should == Rstruct::Int
|
33
|
+
@registry.inherits.should == [Rstruct.default_registry]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should inherit types from arbitrary registries" do
|
37
|
+
begin
|
38
|
+
@registry.inherits.unshift(@preg)
|
39
|
+
c=Rstruct::Type.new(:some_parent_reg_type, :register => @preg)
|
40
|
+
@preg[:some_parent_reg_type].should == c
|
41
|
+
@registry[:some_parent_reg_type].should == c
|
42
|
+
Rstruct.default_registry[c].should be_nil
|
43
|
+
ensure
|
44
|
+
@registry.inherits.delete(@preg)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should register types only to its own registry" do
|
50
|
+
typ = :"test_parent_reg_#{@registry.name}_1"
|
51
|
+
obj=Class.new(Rstruct::Type)
|
52
|
+
|
53
|
+
@registry.register(obj,typ)
|
54
|
+
@registry[typ].should == obj
|
55
|
+
Rstruct.default_registry[typ].should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it_should_behave_like "a basic Rstruct registry"
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|