rstruct 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|