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 CHANGED
@@ -1 +1,2 @@
1
1
  Gemfile.lock
2
+ wiki
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 2.0.0
5
+ - jruby
@@ -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
- [http://bindata.rubyforge.org/manual.html](http://bindata.rubyforge.org/manual.html)
52
+ [Read the wiki](http://github.com/dmendel/bindata/wiki).
46
53
 
47
54
  -or-
48
55
 
@@ -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::Uint32Be.new(:assert => 42, :value => 42)
413
- obj = BinData::Uint32Be.new(:asserted_value => 42)
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
@@ -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
@@ -1,9 +1,10 @@
1
1
  # BinData -- Binary data manipulator.
2
- # Copyright (c) 2007 - 2013 Dion Mendel.
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 - 2013 Dion Mendel.
35
+ # Copyright (c) 2007 - 2014 Dion Mendel.
@@ -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
@@ -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
  }