bindata 1.6.0 → 1.8.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.
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/ChangeLog.rdoc +9 -0
- data/README.md +8 -1
- data/doc/manual.md +27 -2
- data/examples/tcp_ip.rb +179 -0
- data/lib/bindata.rb +4 -2
- data/lib/bindata/base.rb +2 -2
- data/lib/bindata/buffer.rb +119 -0
- data/lib/bindata/dsl.rb +41 -0
- data/lib/bindata/io.rb +321 -190
- data/lib/bindata/primitive.rb +6 -0
- data/lib/bindata/record.rb +1 -37
- data/lib/bindata/registry.rb +27 -23
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/virtual.rb +25 -19
- data/test/buffer_test.rb +144 -0
- data/test/common.rb +1 -1
- data/test/io_test.rb +117 -51
- data/test/lazy_test.rb +12 -12
- data/test/primitive_test.rb +20 -0
- data/test/registry_test.rb +23 -0
- data/test/virtual_test.rb +38 -0
- metadata +9 -4
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/ChangeLog.rdoc
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
== Version 1.8.0 (2014-01-06)
|
4
|
+
|
5
|
+
* Allow custom types to have endian, not just numerics.
|
6
|
+
* Added missing field to TCP example. Thanks to Bertrand Paquet.
|
7
|
+
* Made tests compatible with JRuby. Thanks to Charles Oliver Nutter.
|
8
|
+
* Support travis ci for those that use it. Thanks to Charles Oliver Nutter.
|
9
|
+
* Added Buffer for easier handling of nested streams.
|
10
|
+
* Added Virtual field.
|
11
|
+
|
3
12
|
== Version 1.6.0 (2013-09-02)
|
4
13
|
|
5
14
|
* Added license to .gemspec
|
data/README.md
CHANGED
@@ -28,6 +28,13 @@ r = Rectangle.read(io)
|
|
28
28
|
puts "Rectangle #{r.name} is #{r.width} x #{r.height}"
|
29
29
|
```
|
30
30
|
|
31
|
+
BinData provides a _declarative_ way to read and write structured binary data.
|
32
|
+
|
33
|
+
This means the programmer specifies *what* the format of the binary
|
34
|
+
data is, and BinData works out *how* to read and write data in this
|
35
|
+
format. It is an easier (and more readable) alternative to
|
36
|
+
ruby's `#pack` and `#unpack` methods.
|
37
|
+
|
31
38
|
BinData makes it easy to create new data types. It supports all the common
|
32
39
|
primitive datatypes that are found in structured binary data formats. Support
|
33
40
|
for dependent and variable length fields is built in.
|
@@ -42,7 +49,7 @@ for dependent and variable length fields is built in.
|
|
42
49
|
|
43
50
|
# Documentation
|
44
51
|
|
45
|
-
[
|
52
|
+
[Read the wiki](http://github.com/dmendel/bindata/wiki).
|
46
53
|
|
47
54
|
-or-
|
48
55
|
|
data/doc/manual.md
CHANGED
@@ -196,6 +196,31 @@ The increase in clarity can be seen with the above example. The
|
|
196
196
|
`endian` keyword will cascade to nested types, as illustrated with the
|
197
197
|
array in the above example.
|
198
198
|
|
199
|
+
The endian keyword can also be used to identify custom types that have
|
200
|
+
endianness. To do this, the class name of the custom types must end with `Le`
|
201
|
+
for little endian, and `Be` for big endian.
|
202
|
+
|
203
|
+
class CoordLe < BinData::Record
|
204
|
+
endian :little
|
205
|
+
int16 :x
|
206
|
+
int16 :y
|
207
|
+
end
|
208
|
+
|
209
|
+
class CoordBe < BinData::Record
|
210
|
+
endian :big
|
211
|
+
int16 :x
|
212
|
+
int16 :y
|
213
|
+
end
|
214
|
+
|
215
|
+
class Rectangle < BinData::Record
|
216
|
+
endian :little
|
217
|
+
|
218
|
+
coord :upper_left
|
219
|
+
coord :lower_right
|
220
|
+
end
|
221
|
+
{:ruby}
|
222
|
+
|
223
|
+
|
199
224
|
## Dependencies between fields
|
200
225
|
|
201
226
|
A common occurence in binary file formats is one field depending upon
|
@@ -409,8 +434,8 @@ There are several parameters that are specific to all primitives.
|
|
409
434
|
both `:assert` and `:value` have the same values. The following
|
410
435
|
are equivalent.
|
411
436
|
|
412
|
-
obj = BinData::
|
413
|
-
obj = BinData::
|
437
|
+
obj = BinData::Uint32be.new(:assert => 42, :value => 42)
|
438
|
+
obj = BinData::Uint32be.new(:asserted_value => 42)
|
414
439
|
{:ruby}
|
415
440
|
|
416
441
|
## Numerics
|
data/examples/tcp_ip.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
|
3
|
+
# This is a simple protocol analyser for examining IPv4 packets
|
4
|
+
# captured with libpcap on an ethernet network.
|
5
|
+
#
|
6
|
+
# The dump file can be obtained like this:
|
7
|
+
#
|
8
|
+
# sudo tcpdump -i eth0 -s 0 -n -w dump.pcap
|
9
|
+
#
|
10
|
+
|
11
|
+
|
12
|
+
# Present MAC addresses in a human readable way
|
13
|
+
class MacAddr < BinData::Primitive
|
14
|
+
array :octets, :type => :uint8, :initial_length => 6
|
15
|
+
|
16
|
+
def set(val)
|
17
|
+
ints = val.split(/\./).collect { |int| int.to_i }
|
18
|
+
self.octets = ints
|
19
|
+
end
|
20
|
+
|
21
|
+
def get
|
22
|
+
self.octets.collect { |octet| "%02x" % octet }.join(":")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Present IP addresses in a human readable way
|
27
|
+
class IPAddr < BinData::Primitive
|
28
|
+
array :octets, :type => :uint8, :initial_length => 4
|
29
|
+
|
30
|
+
def set(val)
|
31
|
+
ints = val.split(/\./).collect { |int| int.to_i }
|
32
|
+
self.octets = ints
|
33
|
+
end
|
34
|
+
|
35
|
+
def get
|
36
|
+
self.octets.collect { |octet| "%d" % octet }.join(".")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TCP Protocol Data Unit
|
41
|
+
class TCP_PDU < BinData::Record
|
42
|
+
mandatory_parameter :packet_length
|
43
|
+
|
44
|
+
endian :big
|
45
|
+
|
46
|
+
uint16 :src_port
|
47
|
+
uint16 :dst_port
|
48
|
+
uint32 :seq
|
49
|
+
uint32 :ack_seq
|
50
|
+
bit4 :doff
|
51
|
+
bit4 :res1
|
52
|
+
bit2 :res2
|
53
|
+
bit1 :urg
|
54
|
+
bit1 :ack
|
55
|
+
bit1 :psh
|
56
|
+
bit1 :rst
|
57
|
+
bit1 :syn
|
58
|
+
bit1 :fin
|
59
|
+
uint16 :window
|
60
|
+
uint16 :checksum
|
61
|
+
uint16 :urg_ptr
|
62
|
+
string :options, :read_length => :options_length_in_bytes
|
63
|
+
string :payload, :read_length => lambda { packet_length - payload.rel_offset }
|
64
|
+
|
65
|
+
def options_length_in_bytes
|
66
|
+
(doff - 5 ) * 4
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# UDP Protocol Data Unit
|
71
|
+
class UDP_PDU < BinData::Record
|
72
|
+
mandatory_parameter :packet_length
|
73
|
+
|
74
|
+
endian :big
|
75
|
+
|
76
|
+
uint16 :src_port
|
77
|
+
uint16 :dst_port
|
78
|
+
uint16 :len
|
79
|
+
uint16 :checksum
|
80
|
+
string :payload, :read_length => lambda { packet_length - payload.rel_offset }
|
81
|
+
end
|
82
|
+
|
83
|
+
# IP Protocol Data Unit
|
84
|
+
class IP_PDU < BinData::Record
|
85
|
+
endian :big
|
86
|
+
|
87
|
+
bit4 :version, :asserted_value => 4
|
88
|
+
bit4 :header_length
|
89
|
+
uint8 :tos
|
90
|
+
uint16 :total_length
|
91
|
+
uint16 :ident
|
92
|
+
bit3 :flags
|
93
|
+
bit13 :frag_offset
|
94
|
+
uint8 :ttl
|
95
|
+
uint8 :protocol
|
96
|
+
uint16 :checksum
|
97
|
+
ip_addr :src_addr
|
98
|
+
ip_addr :dest_addr
|
99
|
+
string :options, :read_length => :options_length_in_bytes
|
100
|
+
choice :payload, :selection => :protocol do
|
101
|
+
tcp_pdu 6, :packet_length => :payload_length_in_bytes
|
102
|
+
udp_pdu 17, :packet_length => :payload_length_in_bytes
|
103
|
+
string :default, :read_length => :payload_length_in_bytes
|
104
|
+
end
|
105
|
+
|
106
|
+
def header_length_in_bytes
|
107
|
+
header_length * 4
|
108
|
+
end
|
109
|
+
|
110
|
+
def options_length_in_bytes
|
111
|
+
header_length_in_bytes - options.rel_offset
|
112
|
+
end
|
113
|
+
|
114
|
+
def payload_length_in_bytes
|
115
|
+
total_length - header_length_in_bytes
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Ethernet Frame - NOTE only ipv4 is supported
|
120
|
+
class Ether < BinData::Record
|
121
|
+
IPV4 = 0x0800
|
122
|
+
|
123
|
+
endian :big
|
124
|
+
mac_addr :dst
|
125
|
+
mac_addr :src
|
126
|
+
uint16 :ether_type
|
127
|
+
choice :payload, :selection => :ether_type do
|
128
|
+
ip_pdu IPV4
|
129
|
+
rest :default
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Pcap
|
134
|
+
def initialize(filename)
|
135
|
+
@filename = filename
|
136
|
+
end
|
137
|
+
|
138
|
+
def each_record
|
139
|
+
File.open(@filename) do |io|
|
140
|
+
file = PcapFile.read(io)
|
141
|
+
file.records.each do |rec|
|
142
|
+
yield rec.data
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class PcapFile < BinData::Record
|
148
|
+
endian :little
|
149
|
+
|
150
|
+
struct :head do
|
151
|
+
uint32 :magic
|
152
|
+
uint16 :major
|
153
|
+
uint16 :minor
|
154
|
+
int32 :this_zone
|
155
|
+
uint32 :sig_figs
|
156
|
+
uint32 :snaplen
|
157
|
+
uint32 :linktype
|
158
|
+
end
|
159
|
+
|
160
|
+
array :records, :read_until => :eof do
|
161
|
+
uint32 :ts_sec
|
162
|
+
uint32 :ts_usec
|
163
|
+
uint32 :incl_len
|
164
|
+
uint32 :orig_len
|
165
|
+
string :data, :length => :incl_len
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
require 'pp'
|
171
|
+
unless File.exist?('dump.pcap')
|
172
|
+
puts "No dump file found. Create one with: sudo tcpdump -i eth0 -s 0 -n -w dump.pcap"
|
173
|
+
exit 1
|
174
|
+
end
|
175
|
+
|
176
|
+
cap = Pcap.new('dump.pcap')
|
177
|
+
cap.each_record do |str|
|
178
|
+
pp Ether.read(str)
|
179
|
+
end
|
data/lib/bindata.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# BinData -- Binary data manipulator.
|
2
|
-
# Copyright (c) 2007 -
|
2
|
+
# Copyright (c) 2007 - 2014 Dion Mendel.
|
3
3
|
|
4
4
|
require 'bindata/version'
|
5
5
|
require 'bindata/array'
|
6
6
|
require 'bindata/bits'
|
7
|
+
require 'bindata/buffer'
|
7
8
|
require 'bindata/choice'
|
8
9
|
require 'bindata/count_bytes_remaining'
|
9
10
|
require 'bindata/float'
|
@@ -16,6 +17,7 @@ require 'bindata/string'
|
|
16
17
|
require 'bindata/stringz'
|
17
18
|
require 'bindata/struct'
|
18
19
|
require 'bindata/trace'
|
20
|
+
require 'bindata/virtual'
|
19
21
|
require 'bindata/alignment'
|
20
22
|
require 'bindata/deprecated'
|
21
23
|
|
@@ -30,4 +32,4 @@ require 'bindata/deprecated'
|
|
30
32
|
#
|
31
33
|
# BinData is released under the same license as Ruby.
|
32
34
|
#
|
33
|
-
# Copyright (c) 2007 -
|
35
|
+
# Copyright (c) 2007 - 2014 Dion Mendel.
|
data/lib/bindata/base.rb
CHANGED
@@ -161,7 +161,7 @@ module BinData
|
|
161
161
|
|
162
162
|
# Reads data into this data object.
|
163
163
|
def read(io)
|
164
|
-
io = BinData::IO.new(io) unless BinData::IO === io
|
164
|
+
io = BinData::IO::Read.new(io) unless BinData::IO::Read === io
|
165
165
|
|
166
166
|
@in_read = true
|
167
167
|
clear
|
@@ -184,7 +184,7 @@ module BinData
|
|
184
184
|
|
185
185
|
# Writes the value for this data object to +io+.
|
186
186
|
def write(io)
|
187
|
-
io = BinData::IO.new(io) unless BinData::IO === io
|
187
|
+
io = BinData::IO::Write.new(io) unless BinData::IO::Write === io
|
188
188
|
|
189
189
|
do_write(io)
|
190
190
|
io.flush
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
require 'bindata/dsl'
|
3
|
+
|
4
|
+
module BinData
|
5
|
+
# A Buffer is conceptually a substream within a data stream. It has a
|
6
|
+
# defined size and it will always read or write the exact number of bytes to
|
7
|
+
# fill the buffer. Short reads will skip over unused bytes and short writes
|
8
|
+
# will pad the substream with "\0" bytes.
|
9
|
+
#
|
10
|
+
# require 'bindata'
|
11
|
+
#
|
12
|
+
# obj = BinData::Buffer.new(:length => 5, :type => [:string, {:value => "abc"}])
|
13
|
+
# obj.to_binary_s #=> "abc\000\000"
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# class MyBuffer < BinData::Buffer
|
17
|
+
# default_parameter :length => 8
|
18
|
+
#
|
19
|
+
# endian :little
|
20
|
+
#
|
21
|
+
# uint16 :num1
|
22
|
+
# uint16 :num2
|
23
|
+
# # padding occurs here
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# obj = MyBuffer.read("\001\000\002\000\000\000\000\000")
|
27
|
+
# obj.num1 #=> 1
|
28
|
+
# obj.num1 #=> 2
|
29
|
+
# obj.num_bytes #=> 8
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# class StringTable < BinData::Record
|
33
|
+
# endian :little
|
34
|
+
#
|
35
|
+
# uint16 :table_size_in_bytes
|
36
|
+
# buffer :strings, :length => :table_size_in_bytes do
|
37
|
+
# array :read_until => :eof do
|
38
|
+
# uint8 :len
|
39
|
+
# string :str, :length => :len
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
#
|
45
|
+
# == Parameters
|
46
|
+
#
|
47
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
48
|
+
# an object. These params are:
|
49
|
+
#
|
50
|
+
# <tt>:length</tt>:: The number of bytes in the buffer.
|
51
|
+
# <tt>:type</tt>:: The single type inside the buffer. Use a struct if
|
52
|
+
# multiple fields are required.
|
53
|
+
class Buffer < BinData::Base
|
54
|
+
include DSLMixin
|
55
|
+
|
56
|
+
dsl_parser :buffer
|
57
|
+
|
58
|
+
mandatory_parameters :length, :type
|
59
|
+
|
60
|
+
class << self
|
61
|
+
def arg_extractor
|
62
|
+
MultiFieldArgExtractor
|
63
|
+
end
|
64
|
+
|
65
|
+
def sanitize_parameters!(params) #:nodoc:
|
66
|
+
params.merge!(dsl_params)
|
67
|
+
|
68
|
+
if params.needs_sanitizing?(:type)
|
69
|
+
el_type, el_params = params[:type]
|
70
|
+
params[:type] = params.create_sanitized_object_prototype(el_type, el_params)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize_instance
|
76
|
+
@type = get_parameter(:type).instantiate(nil, self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def clear? #:nodoc:
|
80
|
+
@type.clear?
|
81
|
+
end
|
82
|
+
|
83
|
+
def assign(val)
|
84
|
+
@type.assign(val)
|
85
|
+
end
|
86
|
+
|
87
|
+
def snapshot
|
88
|
+
@type.snapshot
|
89
|
+
end
|
90
|
+
|
91
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
92
|
+
@type.respond_to?(symbol, include_private) || super
|
93
|
+
end
|
94
|
+
|
95
|
+
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
96
|
+
base_respond_to?(symbol, include_private)
|
97
|
+
end
|
98
|
+
|
99
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
100
|
+
@type.__send__(symbol, *args, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def do_read(io) #:nodoc:
|
104
|
+
io.with_buffer(eval_parameter(:length)) do
|
105
|
+
@type.do_read(io)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def do_write(io) #:nodoc:
|
110
|
+
io.with_buffer(eval_parameter(:length)) do
|
111
|
+
@type.do_write(io)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def do_num_bytes #:nodoc:
|
116
|
+
eval_parameter(:length)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/bindata/dsl.rb
CHANGED
@@ -1,4 +1,40 @@
|
|
1
1
|
module BinData
|
2
|
+
# Extracts args for Records and Buffers.
|
3
|
+
#
|
4
|
+
# Foo.new(:bar => "baz) is ambiguous as to whether :bar is a value or parameter.
|
5
|
+
#
|
6
|
+
# BaseArgExtractor always assumes :bar is parameter. This extractor correctly
|
7
|
+
# identifies it as value or parameter.
|
8
|
+
class MultiFieldArgExtractor
|
9
|
+
class << self
|
10
|
+
def extract(the_class, the_args)
|
11
|
+
value, parameters, parent = BaseArgExtractor.extract(the_class, the_args)
|
12
|
+
|
13
|
+
if parameters_is_value?(the_class, value, parameters)
|
14
|
+
value = parameters
|
15
|
+
parameters = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
[value, parameters, parent]
|
19
|
+
end
|
20
|
+
|
21
|
+
def parameters_is_value?(the_class, value, parameters)
|
22
|
+
if value.nil? and parameters.length > 0
|
23
|
+
field_names_in_parameters?(the_class, parameters)
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def field_names_in_parameters?(the_class, parameters)
|
30
|
+
field_names = the_class.fields.field_names
|
31
|
+
param_keys = parameters.keys
|
32
|
+
|
33
|
+
(field_names & param_keys).length > 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
2
38
|
module DSLMixin
|
3
39
|
def self.included(base) #:nodoc:
|
4
40
|
base.extend ClassMethods
|
@@ -80,6 +116,8 @@ module BinData
|
|
80
116
|
to_struct_params
|
81
117
|
when :array
|
82
118
|
to_array_params
|
119
|
+
when :buffer
|
120
|
+
to_array_params
|
83
121
|
when :choice
|
84
122
|
to_choice_params
|
85
123
|
when :primitive
|
@@ -110,6 +148,8 @@ module BinData
|
|
110
148
|
[:multiple_fields, :optional_fieldnames, :hidden_fields]
|
111
149
|
when :array
|
112
150
|
[:multiple_fields, :optional_fieldnames]
|
151
|
+
when :buffer
|
152
|
+
[:multiple_fields, :optional_fieldnames]
|
113
153
|
when :choice
|
114
154
|
[:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]
|
115
155
|
when :primitive
|
@@ -157,6 +197,7 @@ module BinData
|
|
157
197
|
def params_from_block(type, &block)
|
158
198
|
bindata_classes = {
|
159
199
|
:array => BinData::Array,
|
200
|
+
:buffer => BinData::Buffer,
|
160
201
|
:choice => BinData::Choice,
|
161
202
|
:struct => BinData::Struct
|
162
203
|
}
|