jbangert-bindata 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/BSDL +22 -0
- data/COPYING +52 -0
- data/ChangeLog.rdoc +204 -0
- data/Gemfile +2 -0
- data/INSTALL +11 -0
- data/NEWS.rdoc +164 -0
- data/README.md +54 -0
- data/Rakefile +13 -0
- data/bindata.gemspec +31 -0
- data/doc/manual.haml +407 -0
- data/doc/manual.md +1649 -0
- data/examples/NBT.txt +149 -0
- data/examples/gzip.rb +161 -0
- data/examples/ip_address.rb +22 -0
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +178 -0
- data/lib/bindata.rb +33 -0
- data/lib/bindata/alignment.rb +83 -0
- data/lib/bindata/array.rb +335 -0
- data/lib/bindata/base.rb +388 -0
- data/lib/bindata/base_primitive.rb +214 -0
- data/lib/bindata/bits.rb +87 -0
- data/lib/bindata/choice.rb +216 -0
- data/lib/bindata/count_bytes_remaining.rb +35 -0
- data/lib/bindata/deprecated.rb +50 -0
- data/lib/bindata/dsl.rb +312 -0
- data/lib/bindata/float.rb +80 -0
- data/lib/bindata/int.rb +184 -0
- data/lib/bindata/io.rb +274 -0
- data/lib/bindata/lazy.rb +105 -0
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/params.rb +135 -0
- data/lib/bindata/primitive.rb +135 -0
- data/lib/bindata/record.rb +110 -0
- data/lib/bindata/registry.rb +92 -0
- data/lib/bindata/rest.rb +35 -0
- data/lib/bindata/sanitize.rb +290 -0
- data/lib/bindata/skip.rb +48 -0
- data/lib/bindata/string.rb +145 -0
- data/lib/bindata/stringz.rb +96 -0
- data/lib/bindata/struct.rb +388 -0
- data/lib/bindata/trace.rb +94 -0
- data/lib/bindata/version.rb +3 -0
- data/setup.rb +1585 -0
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +331 -0
- data/spec/base_primitive_spec.rb +238 -0
- data/spec/base_spec.rb +376 -0
- data/spec/bits_spec.rb +163 -0
- data/spec/choice_spec.rb +263 -0
- data/spec/count_bytes_remaining_spec.rb +38 -0
- data/spec/deprecated_spec.rb +31 -0
- data/spec/example.rb +21 -0
- data/spec/float_spec.rb +37 -0
- data/spec/int_spec.rb +216 -0
- data/spec/io_spec.rb +352 -0
- data/spec/lazy_spec.rb +217 -0
- data/spec/primitive_spec.rb +202 -0
- data/spec/record_spec.rb +530 -0
- data/spec/registry_spec.rb +108 -0
- data/spec/rest_spec.rb +26 -0
- data/spec/skip_spec.rb +27 -0
- data/spec/spec_common.rb +58 -0
- data/spec/string_spec.rb +300 -0
- data/spec/stringz_spec.rb +118 -0
- data/spec/struct_spec.rb +350 -0
- data/spec/system_spec.rb +380 -0
- data/tasks/manual.rake +36 -0
- data/tasks/rspec.rake +17 -0
- metadata +208 -0
data/lib/bindata/skip.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "bindata/base_primitive"
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# Skip will skip over bytes from the input stream. If the stream is not
|
5
|
+
# seekable, then the bytes are consumed and discarded.
|
6
|
+
#
|
7
|
+
# When writing, skip will write <tt>:length</tt> number of zero bytes.
|
8
|
+
#
|
9
|
+
# require 'bindata'
|
10
|
+
#
|
11
|
+
# class A < BinData::Record
|
12
|
+
# skip :length => 5
|
13
|
+
# string :a, :read_length => 5
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# obj = A.read("abcdefghij")
|
17
|
+
# obj.a #=> "fghij"
|
18
|
+
#
|
19
|
+
# == Parameters
|
20
|
+
#
|
21
|
+
# Skip objects accept all the params that BinData::BasePrimitive
|
22
|
+
# does, as well as the following:
|
23
|
+
#
|
24
|
+
# <tt>:length</tt>:: The number of bytes to skip.
|
25
|
+
#
|
26
|
+
class Skip < BinData::BasePrimitive
|
27
|
+
|
28
|
+
mandatory_parameter :length
|
29
|
+
|
30
|
+
#---------------
|
31
|
+
private
|
32
|
+
|
33
|
+
def value_to_binary_string(val)
|
34
|
+
len = eval_parameter(:length)
|
35
|
+
"\000" * len
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_and_return_value(io)
|
39
|
+
len = eval_parameter(:length)
|
40
|
+
io.seekbytes(len)
|
41
|
+
""
|
42
|
+
end
|
43
|
+
|
44
|
+
def sensible_default
|
45
|
+
""
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "bindata/base_primitive"
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A String is a sequence of bytes. This is the same as strings in Ruby 1.8.
|
5
|
+
# The issue of character encoding is ignored by this class.
|
6
|
+
#
|
7
|
+
# require 'bindata'
|
8
|
+
#
|
9
|
+
# data = "abcdefghij"
|
10
|
+
#
|
11
|
+
# obj = BinData::String.new(:read_length => 5)
|
12
|
+
# obj.read(data)
|
13
|
+
# obj #=> "abcde"
|
14
|
+
#
|
15
|
+
# obj = BinData::String.new(:length => 6)
|
16
|
+
# obj.read(data)
|
17
|
+
# obj #=> "abcdef"
|
18
|
+
# obj.assign("abcdefghij")
|
19
|
+
# obj #=> "abcdef"
|
20
|
+
# obj.assign("abcd")
|
21
|
+
# obj #=> "abcd\000\000"
|
22
|
+
#
|
23
|
+
# obj = BinData::String.new(:length => 6, :trim_padding => true)
|
24
|
+
# obj.assign("abcd")
|
25
|
+
# obj #=> "abcd"
|
26
|
+
# obj.to_binary_s #=> "abcd\000\000"
|
27
|
+
#
|
28
|
+
# obj = BinData::String.new(:length => 6, :pad_byte => 'A')
|
29
|
+
# obj.assign("abcd")
|
30
|
+
# obj #=> "abcdAA"
|
31
|
+
# obj.to_binary_s #=> "abcdAA"
|
32
|
+
#
|
33
|
+
# == Parameters
|
34
|
+
#
|
35
|
+
# String objects accept all the params that BinData::BasePrimitive
|
36
|
+
# does, as well as the following:
|
37
|
+
#
|
38
|
+
# <tt>:read_length</tt>:: The length in bytes to use when reading a value.
|
39
|
+
# <tt>:length</tt>:: The fixed length of the string. If a shorter
|
40
|
+
# string is set, it will be padded to this length.
|
41
|
+
# <tt>:pad_byte</tt>:: The byte to use when padding a string to a
|
42
|
+
# set length. Valid values are Integers and
|
43
|
+
# Strings of length 1. "\0" is the default.
|
44
|
+
# <tt>:pad_front</tt>:: Signifies that the padding occurs at the front
|
45
|
+
# of the string rather than the end. Default
|
46
|
+
# is false.
|
47
|
+
# <tt>:trim_padding</tt>:: Boolean, default false. If set, #value will
|
48
|
+
# return the value with all pad_bytes trimmed
|
49
|
+
# from the end of the string. The value will
|
50
|
+
# not be trimmed when writing.
|
51
|
+
class String < BinData::BasePrimitive
|
52
|
+
|
53
|
+
optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left
|
54
|
+
default_parameters :pad_byte => "\0"
|
55
|
+
mutually_exclusive_parameters :read_length, :length
|
56
|
+
mutually_exclusive_parameters :length, :value
|
57
|
+
|
58
|
+
class << self
|
59
|
+
|
60
|
+
def sanitize_parameters!(params) #:nodoc:
|
61
|
+
params.warn_replacement_parameter(:initial_length, :read_length)
|
62
|
+
|
63
|
+
params.warn_renamed_parameter(:pad_char, :pad_byte) # Remove this line in the future
|
64
|
+
|
65
|
+
if params.has_parameter?(:pad_left)
|
66
|
+
params[:pad_front] = params.delete(:pad_left)
|
67
|
+
end
|
68
|
+
|
69
|
+
if params.has_parameter?(:pad_byte)
|
70
|
+
byte = params[:pad_byte]
|
71
|
+
params[:pad_byte] = sanitized_pad_byte(byte)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#-------------
|
76
|
+
private
|
77
|
+
|
78
|
+
def sanitized_pad_byte(byte)
|
79
|
+
result = byte.is_a?(Integer) ? byte.chr : byte.to_s
|
80
|
+
len = result.respond_to?(:bytesize) ? result.bytesize : result.length
|
81
|
+
if len > 1
|
82
|
+
raise ArgumentError, ":pad_byte must not contain more than 1 byte"
|
83
|
+
end
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def assign(val)
|
89
|
+
super(binary_string(val))
|
90
|
+
end
|
91
|
+
|
92
|
+
def snapshot
|
93
|
+
# override to trim padding
|
94
|
+
result = super
|
95
|
+
result = clamp_to_length(result)
|
96
|
+
|
97
|
+
if get_parameter(:trim_padding)
|
98
|
+
result = trim_padding(result)
|
99
|
+
end
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
#---------------
|
104
|
+
private
|
105
|
+
|
106
|
+
def clamp_to_length(str)
|
107
|
+
str = binary_string(str)
|
108
|
+
|
109
|
+
len = eval_parameter(:length) || str.length
|
110
|
+
if str.length == len
|
111
|
+
str
|
112
|
+
elsif str.length > len
|
113
|
+
str.slice(0, len)
|
114
|
+
else
|
115
|
+
padding = (eval_parameter(:pad_byte) * (len - str.length))
|
116
|
+
if get_parameter(:pad_front)
|
117
|
+
padding + str
|
118
|
+
else
|
119
|
+
str + padding
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def trim_padding(str)
|
125
|
+
if get_parameter(:pad_front)
|
126
|
+
str.sub(/\A#{eval_parameter(:pad_byte)}*/, "")
|
127
|
+
else
|
128
|
+
str.sub(/#{eval_parameter(:pad_byte)}*\z/, "")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def value_to_binary_string(val)
|
133
|
+
clamp_to_length(val)
|
134
|
+
end
|
135
|
+
|
136
|
+
def read_and_return_value(io)
|
137
|
+
len = eval_parameter(:read_length) || eval_parameter(:length) || 0
|
138
|
+
io.readbytes(len)
|
139
|
+
end
|
140
|
+
|
141
|
+
def sensible_default
|
142
|
+
""
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "bindata/base_primitive"
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# A BinData::Stringz object is a container for a zero ("\0") terminated
|
5
|
+
# string.
|
6
|
+
#
|
7
|
+
# For convenience, the zero terminator is not necessary when setting the
|
8
|
+
# value. Likewise, the returned value will not be zero terminated.
|
9
|
+
#
|
10
|
+
# require 'bindata'
|
11
|
+
#
|
12
|
+
# data = "abcd\x00efgh"
|
13
|
+
#
|
14
|
+
# obj = BinData::Stringz.new
|
15
|
+
# obj.read(data)
|
16
|
+
# obj.snapshot #=> "abcd"
|
17
|
+
# obj.num_bytes #=> 5
|
18
|
+
# obj.to_binary_s #=> "abcd\000"
|
19
|
+
#
|
20
|
+
# == Parameters
|
21
|
+
#
|
22
|
+
# Stringz objects accept all the params that BinData::BasePrimitive
|
23
|
+
# does, as well as the following:
|
24
|
+
#
|
25
|
+
# <tt>:max_length</tt>:: The maximum length of the string including the zero
|
26
|
+
# byte.
|
27
|
+
class Stringz < BinData::BasePrimitive
|
28
|
+
|
29
|
+
optional_parameters :max_length
|
30
|
+
|
31
|
+
def assign(val)
|
32
|
+
super(binary_string(val))
|
33
|
+
end
|
34
|
+
|
35
|
+
def snapshot
|
36
|
+
# override to always remove trailing zero bytes
|
37
|
+
result = super
|
38
|
+
trim_and_zero_terminate(result).chomp("\0")
|
39
|
+
end
|
40
|
+
|
41
|
+
#---------------
|
42
|
+
private
|
43
|
+
|
44
|
+
def value_to_binary_string(val)
|
45
|
+
trim_and_zero_terminate(val)
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_and_return_value(io)
|
49
|
+
max_length = eval_parameter(:max_length)
|
50
|
+
str = ""
|
51
|
+
i = 0
|
52
|
+
ch = nil
|
53
|
+
|
54
|
+
# read until zero byte or we have read in the max number of bytes
|
55
|
+
while ch != "\0" and i != max_length
|
56
|
+
ch = io.readbytes(1)
|
57
|
+
str << ch
|
58
|
+
i += 1
|
59
|
+
end
|
60
|
+
|
61
|
+
trim_and_zero_terminate(str)
|
62
|
+
end
|
63
|
+
|
64
|
+
def sensible_default
|
65
|
+
""
|
66
|
+
end
|
67
|
+
|
68
|
+
def trim_and_zero_terminate(str)
|
69
|
+
result = binary_string(str)
|
70
|
+
truncate_after_first_zero_byte!(result)
|
71
|
+
trim_to!(result, eval_parameter(:max_length))
|
72
|
+
append_zero_byte_if_needed!(result)
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
def truncate_after_first_zero_byte!(str)
|
77
|
+
str.sub!(/([^\0]*\0).*/, '\1')
|
78
|
+
end
|
79
|
+
|
80
|
+
def trim_to!(str, max_length = nil)
|
81
|
+
if max_length
|
82
|
+
max_length = 1 if max_length < 1
|
83
|
+
str.slice!(max_length)
|
84
|
+
if str.length == max_length and str[-1, 1] != "\0"
|
85
|
+
str[-1, 1] = "\0"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def append_zero_byte_if_needed!(str)
|
91
|
+
if str.length == 0 or str[-1, 1] != "\0"
|
92
|
+
str << "\0"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
|
5
|
+
class Base
|
6
|
+
optional_parameter :onlyif # Used by Struct
|
7
|
+
end
|
8
|
+
|
9
|
+
# A Struct is an ordered collection of named data objects.
|
10
|
+
#
|
11
|
+
# require 'bindata'
|
12
|
+
#
|
13
|
+
# class Tuple < BinData::Record
|
14
|
+
# int8 :x
|
15
|
+
# int8 :y
|
16
|
+
# int8 :z
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# obj = BinData::Struct.new(:hide => :a,
|
20
|
+
# :fields => [ [:int32le, :a],
|
21
|
+
# [:int16le, :b],
|
22
|
+
# [:tuple, :s] ])
|
23
|
+
# obj.field_names =># ["b", "s"]
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# == Parameters
|
27
|
+
#
|
28
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
29
|
+
# an object. These params are:
|
30
|
+
#
|
31
|
+
# <tt>:fields</tt>:: An array specifying the fields for this struct.
|
32
|
+
# Each element of the array is of the form [type, name,
|
33
|
+
# params]. Type is a symbol representing a registered
|
34
|
+
# type. Name is the name of this field. Params is an
|
35
|
+
# optional hash of parameters to pass to this field
|
36
|
+
# when instantiating it. If name is "" or nil, then
|
37
|
+
# that field is anonymous and behaves as a hidden field.
|
38
|
+
# <tt>:hide</tt>:: A list of the names of fields that are to be hidden
|
39
|
+
# from the outside world. Hidden fields don't appear
|
40
|
+
# in #snapshot or #field_names but are still accessible
|
41
|
+
# by name.
|
42
|
+
# <tt>:endian</tt>:: Either :little or :big. This specifies the default
|
43
|
+
# endian of any numerics in this struct, or in any
|
44
|
+
# nested data objects.
|
45
|
+
#
|
46
|
+
# == Field Parameters
|
47
|
+
#
|
48
|
+
# Fields may have have extra parameters as listed below:
|
49
|
+
#
|
50
|
+
# [<tt>:onlyif</tt>] Used to indicate a data object is optional.
|
51
|
+
# if +false+, this object will not be included in any
|
52
|
+
# calls to #read, #write, #num_bytes or #snapshot.
|
53
|
+
class Struct < BinData::Base
|
54
|
+
|
55
|
+
mandatory_parameter :fields
|
56
|
+
optional_parameters :endian, :hide
|
57
|
+
|
58
|
+
# These reserved words may not be used as field names
|
59
|
+
RESERVED = Hash[*
|
60
|
+
(Hash.instance_methods +
|
61
|
+
%w{alias and begin break case class def defined do else elsif
|
62
|
+
end ensure false for if in module next nil not or redo
|
63
|
+
rescue retry return self super then true undef unless until
|
64
|
+
when while yield} +
|
65
|
+
%w{array element index value} ).collect { |name| name.to_sym }.
|
66
|
+
uniq.collect { |key| [key, true] }.flatten
|
67
|
+
]
|
68
|
+
|
69
|
+
class << self
|
70
|
+
|
71
|
+
def sanitize_parameters!(params) #:nodoc:
|
72
|
+
sanitize_endian(params)
|
73
|
+
sanitize_fields(params)
|
74
|
+
sanitize_hide(params)
|
75
|
+
end
|
76
|
+
|
77
|
+
#-------------
|
78
|
+
private
|
79
|
+
|
80
|
+
def sanitize_endian(params)
|
81
|
+
if params.needs_sanitizing?(:endian)
|
82
|
+
endian = params.create_sanitized_endian(params[:endian])
|
83
|
+
params[:endian] = endian
|
84
|
+
params.endian = endian # sync params[:endian] and params.endian
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def sanitize_fields(params)
|
89
|
+
if params.needs_sanitizing?(:fields)
|
90
|
+
fields = params[:fields]
|
91
|
+
|
92
|
+
params[:fields] = params.create_sanitized_fields
|
93
|
+
fields.each do |ftype, fname, fparams|
|
94
|
+
params[:fields].add_field(ftype, fname, fparams)
|
95
|
+
end
|
96
|
+
|
97
|
+
field_names = sanitized_field_names(params[:fields])
|
98
|
+
ensure_field_names_are_valid(field_names)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def sanitize_hide(params)
|
103
|
+
if params.needs_sanitizing?(:hide) and params.has_parameter?(:fields)
|
104
|
+
field_names = sanitized_field_names(params[:fields])
|
105
|
+
hfield_names = hidden_field_names(params[:hide])
|
106
|
+
|
107
|
+
params[:hide] = (hfield_names & field_names)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def sanitized_field_names(sanitized_fields)
|
112
|
+
sanitized_fields.field_names.compact
|
113
|
+
end
|
114
|
+
|
115
|
+
def hidden_field_names(hidden)
|
116
|
+
(hidden || []).collect do |h|
|
117
|
+
unless Symbol === h
|
118
|
+
warn "Hidden field '#{h}' should be provided as a symbol. Using strings is deprecated"
|
119
|
+
end
|
120
|
+
h.to_sym
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def ensure_field_names_are_valid(field_names)
|
125
|
+
reserved_names = RESERVED
|
126
|
+
|
127
|
+
field_names.each do |name|
|
128
|
+
if self.class.method_defined?(name)
|
129
|
+
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
130
|
+
"as it shadows an existing method.", name)
|
131
|
+
end
|
132
|
+
if reserved_names.include?(name)
|
133
|
+
raise NameError.new("Rename field '#{name}' in #{self}, " +
|
134
|
+
"as it is a reserved name.", name)
|
135
|
+
end
|
136
|
+
if field_names.count(name) != 1
|
137
|
+
raise NameError.new("field '#{name}' in #{self}, " +
|
138
|
+
"is defined multiple times.", name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def initialize_shared_instance
|
145
|
+
@field_names = get_parameter(:fields).field_names.freeze
|
146
|
+
end
|
147
|
+
|
148
|
+
def initialize_instance
|
149
|
+
@field_objs = []
|
150
|
+
end
|
151
|
+
|
152
|
+
def clear #:nodoc:
|
153
|
+
@field_objs.each { |f| f.clear unless f.nil? }
|
154
|
+
end
|
155
|
+
|
156
|
+
def clear? #:nodoc:
|
157
|
+
@field_objs.all? { |f| f.nil? or f.clear? }
|
158
|
+
end
|
159
|
+
|
160
|
+
def assign(val)
|
161
|
+
clear
|
162
|
+
assign_fields(val)
|
163
|
+
end
|
164
|
+
|
165
|
+
def snapshot
|
166
|
+
snapshot = Snapshot.new
|
167
|
+
field_names.each do |name|
|
168
|
+
obj = find_obj_for_name(name)
|
169
|
+
snapshot[name] = obj.snapshot if include_obj(obj)
|
170
|
+
end
|
171
|
+
snapshot
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns a list of the names of all fields accessible through this
|
175
|
+
# object. +include_hidden+ specifies whether to include hidden names
|
176
|
+
# in the listing.
|
177
|
+
def field_names(include_hidden = false)
|
178
|
+
if include_hidden
|
179
|
+
@field_names.compact
|
180
|
+
else
|
181
|
+
hidden = get_parameter(:hide) || []
|
182
|
+
@field_names.compact - hidden
|
183
|
+
end.collect { |x| x.to_s }
|
184
|
+
end
|
185
|
+
|
186
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
187
|
+
@field_names.include?(base_field_name(symbol)) || super
|
188
|
+
end
|
189
|
+
|
190
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
191
|
+
obj = find_obj_for_name(symbol)
|
192
|
+
if obj
|
193
|
+
invoke_field(obj, symbol, args)
|
194
|
+
else
|
195
|
+
super
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def debug_name_of(child) #:nodoc:
|
200
|
+
field_name = @field_names[find_index_of(child)]
|
201
|
+
"#{debug_name}.#{field_name}"
|
202
|
+
end
|
203
|
+
|
204
|
+
def offset_of(child) #:nodoc:
|
205
|
+
instantiate_all_objs
|
206
|
+
sum = sum_num_bytes_below_index(find_index_of(child))
|
207
|
+
child.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
|
208
|
+
end
|
209
|
+
|
210
|
+
def do_read(io) #:nodoc:
|
211
|
+
instantiate_all_objs
|
212
|
+
@field_objs.each { |f| f.do_read(io) if include_obj(f) }
|
213
|
+
end
|
214
|
+
|
215
|
+
def do_write(io) #:nodoc
|
216
|
+
instantiate_all_objs
|
217
|
+
@field_objs.each { |f| f.do_write(io) if include_obj(f) }
|
218
|
+
end
|
219
|
+
|
220
|
+
def do_num_bytes #:nodoc:
|
221
|
+
instantiate_all_objs
|
222
|
+
sum_num_bytes_for_all_fields
|
223
|
+
end
|
224
|
+
|
225
|
+
def [](key)
|
226
|
+
find_obj_for_name(key)
|
227
|
+
end
|
228
|
+
|
229
|
+
def []=(key, value)
|
230
|
+
obj = find_obj_for_name(key)
|
231
|
+
if obj
|
232
|
+
obj.assign(value)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def has_key?(key)
|
237
|
+
@field_names.index(base_field_name(key))
|
238
|
+
end
|
239
|
+
|
240
|
+
def each_pair
|
241
|
+
@field_names.compact.each do |name|
|
242
|
+
yield [name, find_obj_for_name(name)]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
#---------------
|
247
|
+
private
|
248
|
+
|
249
|
+
def base_field_name(name)
|
250
|
+
name.to_s.chomp("=").to_sym
|
251
|
+
end
|
252
|
+
|
253
|
+
def invoke_field(obj, symbol, args)
|
254
|
+
name = symbol.to_s
|
255
|
+
is_writer = (name[-1, 1] == "=")
|
256
|
+
|
257
|
+
if is_writer
|
258
|
+
obj.assign(*args)
|
259
|
+
else
|
260
|
+
obj
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def find_index_of(obj)
|
265
|
+
@field_objs.index { |el| el.equal?(obj) }
|
266
|
+
end
|
267
|
+
|
268
|
+
def find_obj_for_name(name)
|
269
|
+
index = @field_names.index(base_field_name(name))
|
270
|
+
if index
|
271
|
+
instantiate_obj_at(index)
|
272
|
+
@field_objs[index]
|
273
|
+
else
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def instantiate_all_objs
|
279
|
+
@field_names.each_index { |i| instantiate_obj_at(i) }
|
280
|
+
end
|
281
|
+
|
282
|
+
def instantiate_obj_at(index)
|
283
|
+
if @field_objs[index].nil?
|
284
|
+
field = get_parameter(:fields)[index]
|
285
|
+
@field_objs[index] = field.instantiate(nil, self)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def assign_fields(val)
|
290
|
+
src = as_stringified_hash(val)
|
291
|
+
|
292
|
+
@field_names.compact.each do |name|
|
293
|
+
obj = find_obj_for_name(name)
|
294
|
+
if obj and src.has_key?(name)
|
295
|
+
obj.assign(src[name])
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def as_stringified_hash(val)
|
301
|
+
if BinData::Struct === val
|
302
|
+
val
|
303
|
+
elsif val.nil?
|
304
|
+
{}
|
305
|
+
else
|
306
|
+
hash = Snapshot.new
|
307
|
+
val.each_pair { |k,v| hash[k] = v }
|
308
|
+
hash
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def sum_num_bytes_for_all_fields
|
313
|
+
sum_num_bytes_below_index(@field_objs.length)
|
314
|
+
end
|
315
|
+
|
316
|
+
def sum_num_bytes_below_index(index)
|
317
|
+
sum = 0
|
318
|
+
(0...index).each do |i|
|
319
|
+
obj = @field_objs[i]
|
320
|
+
if include_obj(obj)
|
321
|
+
nbytes = obj.do_num_bytes
|
322
|
+
sum = (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
sum
|
327
|
+
end
|
328
|
+
|
329
|
+
def include_obj(obj)
|
330
|
+
not obj.has_parameter?(:onlyif) or obj.eval_parameter(:onlyif)
|
331
|
+
end
|
332
|
+
|
333
|
+
if RUBY_VERSION <= "1.9"
|
334
|
+
module OrderedHash #:nodoc:
|
335
|
+
def keys
|
336
|
+
@order ||= []
|
337
|
+
k = super
|
338
|
+
@order & k
|
339
|
+
end
|
340
|
+
|
341
|
+
def []=(key, value)
|
342
|
+
@order ||= []
|
343
|
+
@order << key
|
344
|
+
super(key, value)
|
345
|
+
end
|
346
|
+
|
347
|
+
def each
|
348
|
+
keys.each do |k|
|
349
|
+
yield [k, self[k]]
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def each_pair
|
354
|
+
each do |el|
|
355
|
+
yield *el
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# A hash that can be accessed via attributes.
|
362
|
+
class Snapshot < ::Hash #:nodoc:
|
363
|
+
include OrderedHash if RUBY_VERSION <= "1.9"
|
364
|
+
|
365
|
+
def has_key?(key)
|
366
|
+
super(key.to_s)
|
367
|
+
end
|
368
|
+
|
369
|
+
def [](key)
|
370
|
+
super(key.to_s)
|
371
|
+
end
|
372
|
+
|
373
|
+
def []=(key, value)
|
374
|
+
if value != nil
|
375
|
+
super(key.to_s, value)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def respond_to?(symbol, include_private = false)
|
380
|
+
has_key?(symbol) || super
|
381
|
+
end
|
382
|
+
|
383
|
+
def method_missing(symbol, *args)
|
384
|
+
self[symbol] || super
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|