bindata 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

@@ -0,0 +1,120 @@
1
+ require 'bindata/base'
2
+
3
+ module BinData
4
+ # A Choice is a collection of data objects of which only one is active
5
+ # at any particular time.
6
+ #
7
+ # require 'bindata'
8
+ # require 'stringio'
9
+ #
10
+ # choices = [ [:int8, {:value => 3}], [:int8, {:value => 5}] ]
11
+ # a = BinData::Choice.new(:choices => choices, :selection => 1)
12
+ # a.value # => 5
13
+ #
14
+ # == Parameters
15
+ #
16
+ # Parameters may be provided at initialisation to control the behaviour of
17
+ # an object. These params are:
18
+ #
19
+ # <tt>:choices</tt>:: An array specifying the possible data objects.
20
+ # The format of the array is a list of symbols
21
+ # representing the data object type. If a choice
22
+ # is to have params passed to it, then it should be
23
+ # provided as [type_symbol, hash_params].
24
+ # <tt>:selection</tt>:: An index into the :choices array which specifies
25
+ # the currently active choice.
26
+ class Choice < Base
27
+
28
+ # Register this class
29
+ register(self.name, self)
30
+
31
+ # These are the parameters used by this class.
32
+ mandatory_parameters :choices, :selection
33
+
34
+ def initialize(params = {}, env = nil)
35
+ super(params, env)
36
+
37
+ # instantiate all choices
38
+ @choices = []
39
+ param(:choices).each do |choice_type, choice_params|
40
+ choice_params ||= {}
41
+ klass = self.class.lookup(choice_type)
42
+ if klass.nil?
43
+ raise TypeError, "unknown type '#{choice_type.id2name}' for #{self}"
44
+ end
45
+ @choices << klass.new(choice_params, create_env)
46
+ end
47
+ end
48
+
49
+ # Resets the internal state to that of a newly created object.
50
+ def clear
51
+ the_choice.clear
52
+ end
53
+
54
+ # Returns if the selected data object is clear?.
55
+ def clear?
56
+ the_choice.clear?
57
+ end
58
+
59
+ # Reads the value of the selected data object from +io+.
60
+ def _do_read(io)
61
+ the_choice.do_read(io)
62
+ end
63
+
64
+ # To be called after calling #do_read.
65
+ def done_read
66
+ the_choice.done_read
67
+ end
68
+
69
+ # Writes the value of the selected data object to +io+.
70
+ def _write(io)
71
+ the_choice.write(io)
72
+ end
73
+
74
+ # Returns the number of bytes it will take to write the
75
+ # selected data object.
76
+ def _num_bytes(what)
77
+ the_choice.num_bytes(what)
78
+ end
79
+
80
+ # Returns a snapshot of the selected data object.
81
+ def snapshot
82
+ the_choice.snapshot
83
+ end
84
+
85
+ # Returns a list of the names of all fields of the selected data object.
86
+ def field_names
87
+ the_choice.field_names
88
+ end
89
+
90
+ # Returns the data object that stores values for +name+.
91
+ def find_obj_for_name(name)
92
+ field_names.include?(name) ? the_choice.find_obj_for_name(name) : nil
93
+ end
94
+
95
+ # Override to include selected data object.
96
+ def respond_to?(symbol, include_private = false)
97
+ super || the_choice.respond_to?(symbol, include_private)
98
+ end
99
+
100
+ def method_missing(symbol, *args)
101
+ if the_choice.respond_to?(symbol)
102
+ the_choice.__send__(symbol, *args)
103
+ else
104
+ super
105
+ end
106
+ end
107
+
108
+ #---------------
109
+ private
110
+
111
+ # Returns the selected data object.
112
+ def the_choice
113
+ index = eval_param(:selection)
114
+ if index < 0 or index >= @choices.length
115
+ raise IndexError, "selection #{index} is out of range"
116
+ end
117
+ @choices[index]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,171 @@
1
+ require 'bindata/single'
2
+
3
+ module BinData
4
+ # Provides a number of classes that contain an integer. The integer
5
+ # is defined by endian, signedness and number of bytes.
6
+
7
+ module BaseUint #:nodoc: all
8
+
9
+ def value=(val)
10
+ super(clamp(val))
11
+ end
12
+
13
+ #---------------
14
+ private
15
+
16
+ def sensible_default
17
+ 0
18
+ end
19
+
20
+ def val_to_str(val)
21
+ _val_to_str(clamp(val))
22
+ end
23
+
24
+ # Clamps +val+ to the range 0 .. max_val
25
+ def clamp(val)
26
+ v = val
27
+ nbytes = val_num_bytes(0)
28
+ min = 0
29
+ max = (1 << (nbytes * 8)) - 1
30
+ val = min if val < min
31
+ val = max if val > max
32
+ val
33
+ end
34
+ end
35
+
36
+ module BaseInt #:nodoc: all
37
+ def uint2int(val)
38
+ nbytes = val_num_bytes(0)
39
+ mask = (1 << (nbytes * 8 - 1)) - 1
40
+ msb = (val >> (nbytes * 8 - 1)) & 0x1
41
+ (msb == 1) ? -(((~val) & mask) + 1) : val & mask
42
+ end
43
+
44
+ def int2uint(val)
45
+ nbytes = val_num_bytes(0)
46
+ mask = (1 << (nbytes * 8)) - 1
47
+ val & mask
48
+ end
49
+
50
+ def value=(val)
51
+ super(clamp(val))
52
+ end
53
+
54
+ #---------------
55
+ private
56
+
57
+ def sensible_default
58
+ 0
59
+ end
60
+
61
+ def val_to_str(val)
62
+ _val_to_str(int2uint(clamp(val)))
63
+ end
64
+
65
+ def read_val(io)
66
+ uint2int(_read_val(io))
67
+ end
68
+
69
+ # Clamps +val+ to the range min_val .. max_val, where min and max
70
+ # are the largest representable integers.
71
+ def clamp(val)
72
+ nbytes = val_num_bytes(0)
73
+ max = (1 << (nbytes * 8 - 1)) - 1
74
+ min = -(max + 1)
75
+ val = min if val < min
76
+ val = max if val > max
77
+ val
78
+ end
79
+ end
80
+
81
+
82
+ # Unsigned 1 byte integer.
83
+ class Uint8 < Single
84
+ include BaseUint
85
+ private
86
+ def val_num_bytes(val) 1 end
87
+ def read_val(io) readbytes(io,1)[0] end
88
+ def _val_to_str(val) val.chr end
89
+ end
90
+
91
+ # Unsigned 2 byte little endian integer.
92
+ class Uint16le < Single
93
+ include BaseUint
94
+ private
95
+ def val_num_bytes(val) 2 end
96
+ def read_val(io) readbytes(io,2).unpack("v")[0] end
97
+ def _val_to_str(val) [val].pack("v") end
98
+ end
99
+
100
+ # Unsigned 2 byte big endian integer.
101
+ class Uint16be < Single
102
+ include BaseUint
103
+ private
104
+ def val_num_bytes(val) 2 end
105
+ def read_val(io) readbytes(io,2).unpack("n")[0] end
106
+ def _val_to_str(val) [val].pack("n") end
107
+ end
108
+
109
+ # Unsigned 4 byte little endian integer.
110
+ class Uint32le < Single
111
+ include BaseUint
112
+ private
113
+ def val_num_bytes(val) 4 end
114
+ def read_val(io) readbytes(io,4).unpack("V")[0] end
115
+ def _val_to_str(val) [val].pack("V") end
116
+ end
117
+
118
+ # Unsigned 4 byte big endian integer.
119
+ class Uint32be < Single
120
+ include BaseUint
121
+ private
122
+ def val_num_bytes(val) 4 end
123
+ def read_val(io) readbytes(io,4).unpack("N")[0] end
124
+ def _val_to_str(val) [val].pack("N") end
125
+ end
126
+
127
+ # Signed 1 byte integer.
128
+ class Int8 < Single
129
+ include BaseInt
130
+ private
131
+ def val_num_bytes(val) 1 end
132
+ def _read_val(io) readbytes(io,1)[0] end
133
+ def _val_to_str(val) val.chr end
134
+ end
135
+
136
+ # Signed 2 byte little endian integer.
137
+ class Int16le < Single
138
+ include BaseInt
139
+ private
140
+ def val_num_bytes(val) 2 end
141
+ def _read_val(io) readbytes(io,2).unpack("v")[0] end
142
+ def _val_to_str(val) [val].pack("v") end
143
+ end
144
+
145
+ # Signed 2 byte big endian integer.
146
+ class Int16be < Single
147
+ include BaseInt
148
+ private
149
+ def val_num_bytes(val) 2 end
150
+ def _read_val(io) readbytes(io,2).unpack("n")[0] end
151
+ def _val_to_str(val) [val].pack("n") end
152
+ end
153
+
154
+ # Signed 4 byte little endian integer.
155
+ class Int32le < Single
156
+ include BaseInt
157
+ private
158
+ def val_num_bytes(val) 4 end
159
+ def _read_val(io) readbytes(io,4).unpack("V")[0] end
160
+ def _val_to_str(val) [val].pack("V") end
161
+ end
162
+
163
+ # Signed 4 byte big endian integer.
164
+ class Int32be < Single
165
+ include BaseInt
166
+ private
167
+ def val_num_bytes(val) 4 end
168
+ def _read_val(io) readbytes(io,4).unpack("N")[0] end
169
+ def _val_to_str(val) [val].pack("N") end
170
+ end
171
+ end
@@ -0,0 +1,71 @@
1
+ module BinData
2
+ # The enviroment in which a lazily evaluated lamba is called. These lambdas
3
+ # are those that are passed to data objects as parameters. Each lambda
4
+ # has access to the following:
5
+ #
6
+ # parent:: the environment of the parent data object
7
+ # params:: any extra parameters that have been passed to the data object.
8
+ # The value of a parameter is either a lambda, a symbol or a
9
+ # literal value (such as a Fixnum).
10
+ # value:: the value of the data object if it is single
11
+ # index:: the index of the data object if it is in an array
12
+ # offset:: the current offset of the IO object when reading
13
+ #
14
+ # Unknown methods are resolved in the context of the parent data object,
15
+ # first as keys in the extra parameters, and secondly as methods in the
16
+ # parent data object. This makes the lambda easier to read as we just write
17
+ # <tt>field</tt> instead of <tt>obj.field</tt>.
18
+ class LazyEvalEnv
19
+ # Creates a new environment. +parent+ is the environment of the
20
+ # parent data object.
21
+ def initialize(parent = nil)
22
+ @parent = parent
23
+ end
24
+ attr_reader :parent
25
+ attr_accessor :data_object, :params, :index, :offset
26
+
27
+ # only accessible by another LazyEvalEnv
28
+ protected :data_object
29
+
30
+ # TODO: offset_of needs to be better thought out
31
+ def offset_of(sym)
32
+ if @parent and @parent.data_object and
33
+ @parent.data_object.respond_to?(:offset_of)
34
+ @parent.data_object.offset_of(sym)
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ # Returns the value of the data object wrapped by this environment.
41
+ def value
42
+ @data_object.respond_to?(:value) ? @data_object.value : nil
43
+ end
44
+
45
+ # Evaluates +obj+ in the context of this environment. Evaluation
46
+ # recurses until it yields a value that is not a symbol or lambda.
47
+ def lazy_eval(obj)
48
+ if obj.is_a? Symbol
49
+ # treat :foo as lambda { foo }
50
+ lazy_eval(__send__(obj))
51
+ elsif obj.respond_to? :arity
52
+ instance_eval(&obj)
53
+ else
54
+ obj
55
+ end
56
+ end
57
+
58
+ def method_missing(symbol, *args)
59
+ if @parent and @parent.params and @parent.params.has_key?(symbol)
60
+ # is there a param with this name?
61
+ @parent.lazy_eval(@parent.params[symbol])
62
+ elsif @parent and @parent.data_object and
63
+ @parent.data_object.respond_to?(symbol)
64
+ # how about a field or method in the parent?
65
+ @parent.data_object.__send__(symbol, *args)
66
+ else
67
+ super
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,37 @@
1
+ require 'singleton'
2
+
3
+ module BinData
4
+ # This registry contains a register of name -> class mappings.
5
+ class Registry
6
+ include Singleton
7
+
8
+ def initialize
9
+ @registry = {}
10
+ end
11
+
12
+ # Registers the mapping of +name+ to +klass+. +name+ is converted
13
+ # from camelCase to underscore style.
14
+ # Returns the converted name
15
+ def register(name, klass)
16
+ # convert camelCase name to underscore style
17
+ underscore_name = name.to_s.sub(/.*::/, "").
18
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
19
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
20
+ tr("-", "_").
21
+ downcase
22
+
23
+ # warn if replacing an existing class
24
+ if $VERBOSE and (existing = @registry[underscore_name])
25
+ warn "warning: replacing registered class #{existing} with #{klass}"
26
+ end
27
+
28
+ @registry[underscore_name] = klass
29
+ underscore_name.dup
30
+ end
31
+
32
+ # Returns the class matching a previously registered +name+.
33
+ def lookup(name)
34
+ @registry[name.to_s]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,170 @@
1
+ require 'bindata/base'
2
+
3
+ module BinData
4
+ # A BinData::Single object is a container for a value that has a particular
5
+ # binary representation. A value corresponds to a primitive type such as
6
+ # as integer, float or string. Only one value can be contained by this
7
+ # object. This value can be read from or written to an IO stream.
8
+ #
9
+ # == Parameters
10
+ #
11
+ # Parameters may be provided at initialisation to control the behaviour of
12
+ # an object. These params include those for BinData::Base as well as:
13
+ #
14
+ # [<tt>:initial_value</tt>] This is the initial value to use before one is
15
+ # either #read or explicitly set with #value=.
16
+ # [<tt>:value</tt>] The object will always have this value.
17
+ # Explicitly calling #value= is prohibited when
18
+ # using this param. In the interval between
19
+ # calls to #do_read and #done_read, #value
20
+ # will return the value of the data read from the
21
+ # IO, not the result of the <tt>:value</tt> param.
22
+ # [<tt>:check_value</tt>] Raise an error unless the value read in meets
23
+ # this criteria. A boolean return indicates
24
+ # success or failure. Any other return is compared
25
+ # to the value just read in.
26
+ class Single < Base
27
+ # These are the parameters used by this class.
28
+ optional_parameters :initial_value, :value, :check_value
29
+
30
+ # Register the names of all subclasses of this class.
31
+ def self.inherited(subclass) #:nodoc:
32
+ register(subclass.name, subclass)
33
+ end
34
+
35
+ def initialize(params = {}, env = nil)
36
+ super(params, env)
37
+ ensure_mutual_exclusion(:initial_value, :value)
38
+ clear
39
+ end
40
+
41
+ # Resets the internal state to that of a newly created object.
42
+ def clear
43
+ @value = nil
44
+ @in_read = false
45
+ end
46
+
47
+ # Returns if the value of this data has been read or explicitly set.
48
+ def clear?
49
+ @value.nil?
50
+ end
51
+
52
+ # Reads the value for this data from +io+.
53
+ def _do_read(io)
54
+ @in_read = true
55
+ @value = read_val(io)
56
+
57
+ # does the value meet expectations?
58
+ if has_param?(:check_value)
59
+ expected = eval_param(:check_value)
60
+ if not expected
61
+ raise ValidityError, "value not as expected"
62
+ elsif @value != expected and expected != true
63
+ raise ValidityError, "value is '#{@value}' but " +
64
+ "expected '#{expected}'"
65
+ end
66
+ end
67
+ end
68
+
69
+ # To be called after calling #do_read.
70
+ def done_read
71
+ @in_read = false
72
+ end
73
+
74
+ # Writes the value for this data to +io+.
75
+ def _write(io)
76
+ raise "can't write whilst reading" if @in_read
77
+ io.write(val_to_str(_value))
78
+ end
79
+
80
+ # Returns the number of bytes it will take to write this data.
81
+ def _num_bytes(ignored)
82
+ val_to_str(_value).length
83
+ end
84
+
85
+ # Returns a snapshot of this data object.
86
+ def snapshot
87
+ value
88
+ end
89
+
90
+ # Single objects don't contain fields so this returns an empty list.
91
+ def field_names
92
+ []
93
+ end
94
+
95
+ # Returns the current value of this data.
96
+ def value
97
+ _value
98
+ end
99
+
100
+ # Sets the value of this data.
101
+ def value=(v)
102
+ # only allow modification if the value isn't predefined
103
+ unless has_param?(:value)
104
+ raise ArgumentError, "can't set a nil value" if v.nil?
105
+ @value = v
106
+ end
107
+ end
108
+
109
+ #---------------
110
+ private
111
+
112
+ # The unmodified value of this data object. Note that #value calls this
113
+ # method. This is so that #value can be overridden in subclasses to
114
+ # modify the value.
115
+ def _value
116
+ # Table of possible preconditions and expected outcome
117
+ # 1. :value and !in_read -> :value
118
+ # 2. :value and in_read -> @value
119
+ # 3. :initial_value and clear? -> :initial_value
120
+ # 4. :initial_value and !clear? -> @value
121
+ # 5. clear? -> sensible_default
122
+ # 6. !clear? -> @value
123
+
124
+ if not @in_read and (evaluated_value = eval_param(:value))
125
+ # rule 1 above
126
+ evaluated_value
127
+ else
128
+ # combining all other rules gives this simplified expression
129
+ @value || eval_param(:value) ||
130
+ eval_param(:initial_value) || sensible_default()
131
+ end
132
+ end
133
+
134
+ # Usuable by subclasses
135
+
136
+ # Reads exactly +n+ bytes from +io+. This should be used by subclasses
137
+ # in preference to <tt>io.read(n)</tt>.
138
+ #
139
+ # If the data read is nil an EOFError is raised.
140
+ #
141
+ # If the data read is too short an IOError is raised.
142
+ def readbytes(io, n)
143
+ str = io.read(n)
144
+ raise EOFError, "End of file reached" if str == nil
145
+ raise IOError, "data truncated" if str.size < n
146
+ str
147
+ end
148
+
149
+ =begin
150
+ # To be implemented by subclasses
151
+
152
+ # Return the string representation that +val+ will take when written.
153
+ def val_to_str(val)
154
+ raise NotImplementedError
155
+ end
156
+
157
+ # Read a number of bytes from +io+ and return the value they represent.
158
+ def read_val(io)
159
+ raise NotImplementedError
160
+ end
161
+
162
+ # Return a sensible default for this data.
163
+ def sensible_default
164
+ raise NotImplementedError
165
+ end
166
+
167
+ # To be implemented by subclasses
168
+ =end
169
+ end
170
+ end