bit-struct 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/History.txt +102 -0
- data/README.txt +189 -0
- data/Rakefile +34 -0
- data/TODO +23 -0
- data/TODO-ALSO +71 -0
- data/examples/ara-player-data.rb +82 -0
- data/examples/bignum.rb +18 -0
- data/examples/bits.rb +19 -0
- data/examples/byte-bdy.rb +30 -0
- data/examples/field-ripper.rb +22 -0
- data/examples/fixed-point.rb +17 -0
- data/examples/ip.rb +81 -0
- data/examples/longlong.rb +30 -0
- data/examples/md.rb +23 -0
- data/examples/modular-def.rb +38 -0
- data/examples/native.rb +31 -0
- data/examples/nested.rb +33 -0
- data/examples/pad.rb +14 -0
- data/examples/ping-recv.rb +25 -0
- data/examples/ping.rb +73 -0
- data/examples/player-data.rb +75 -0
- data/examples/raw.rb +62 -0
- data/examples/rest.rb +30 -0
- data/examples/switch-endian.rb +49 -0
- data/examples/vector.rb +98 -0
- data/lib/bit-struct.rb +6 -0
- data/lib/bit-struct/bit-struct.rb +549 -0
- data/lib/bit-struct/char-field.rb +48 -0
- data/lib/bit-struct/fields.rb +273 -0
- data/lib/bit-struct/float-field.rb +61 -0
- data/lib/bit-struct/hex-octet-field.rb +20 -0
- data/lib/bit-struct/nested-field.rb +76 -0
- data/lib/bit-struct/octet-field.rb +45 -0
- data/lib/bit-struct/pad-field.rb +15 -0
- data/lib/bit-struct/signed-field.rb +258 -0
- data/lib/bit-struct/text-field.rb +44 -0
- data/lib/bit-struct/unsigned-field.rb +248 -0
- data/lib/bit-struct/vector-field.rb +77 -0
- data/lib/bit-struct/vector.rb +173 -0
- data/lib/bit-struct/yaml.rb +69 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test-endian.rb +39 -0
- data/test/test-vector.rb +38 -0
- data/test/test.rb +433 -0
- metadata +126 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'bit-struct'
|
2
|
+
|
3
|
+
class PlayerData < BitStruct
|
4
|
+
|
5
|
+
# type accessor size (bits) description (for #inspect_detailed)
|
6
|
+
unsigned :pid, 32, "Player ID"
|
7
|
+
float :x_position, 32, "X position", :format => "%8.3f"
|
8
|
+
float :y_position, 32, "Y position"
|
9
|
+
float :z_position, 32, "Z position"
|
10
|
+
unsigned :foobar, 32, "Foobar"
|
11
|
+
|
12
|
+
# def self.create(*args)
|
13
|
+
# new(args.pack(field_format))
|
14
|
+
# end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
pd = PlayerData.new(
|
19
|
+
:pid => 400,
|
20
|
+
:x_position => 1,
|
21
|
+
:y_position => 2,
|
22
|
+
:z_position => 3,
|
23
|
+
:foobar => 0b101010
|
24
|
+
)
|
25
|
+
|
26
|
+
p pd
|
27
|
+
p pd.pid
|
28
|
+
p pd.x_position
|
29
|
+
p pd.foobar
|
30
|
+
p String.new(pd)
|
31
|
+
puts pd.inspect_detailed
|
32
|
+
|
33
|
+
params = {
|
34
|
+
:pid => 400,
|
35
|
+
:x_position => 1,
|
36
|
+
:y_position => 2,
|
37
|
+
:z_position => 3,
|
38
|
+
:foobar => 0b101010
|
39
|
+
}
|
40
|
+
param_string = String.new(pd)
|
41
|
+
|
42
|
+
id, x_position, y_position, z_position, foobar =
|
43
|
+
400, 1.0, 2.0, 3.0, 0b101010
|
44
|
+
|
45
|
+
begin
|
46
|
+
require 'timeout'
|
47
|
+
n = 0
|
48
|
+
sec = 10
|
49
|
+
Timeout::timeout(sec) do
|
50
|
+
loop do
|
51
|
+
PlayerData.new params
|
52
|
+
#PlayerData.create id, x_position, y_position, z_position, foobar
|
53
|
+
n += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Timeout::Error
|
57
|
+
puts "creations per second : #{ n / sec }"
|
58
|
+
end
|
59
|
+
|
60
|
+
__END__
|
61
|
+
|
62
|
+
#<PlayerData pid=400, x_position=1, y_position=2, z_position=3, foobar=42>
|
63
|
+
400
|
64
|
+
1
|
65
|
+
42
|
66
|
+
"\000\000\001\220\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000*"
|
67
|
+
PlayerData:
|
68
|
+
Player ID = 400
|
69
|
+
X position = 1
|
70
|
+
Y position = 2
|
71
|
+
Z position = 3
|
72
|
+
Foobar = 42
|
73
|
+
creations per second : 22428 # using params
|
74
|
+
creations per second : 243217 # using param_string
|
75
|
+
creations per second : 94254 # using create
|
data/examples/raw.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "./ip"
|
3
|
+
|
4
|
+
# A more substantial example of sending and receiving RAW packets.
|
5
|
+
|
6
|
+
begin
|
7
|
+
rsock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
|
8
|
+
rescue Errno::EPERM
|
9
|
+
$stderr.puts "Must run #{$0} as root."
|
10
|
+
exit!
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
ssock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
|
15
|
+
unless ssock.getsockopt(Socket::SOL_IP, Socket::IP_HDRINCL)
|
16
|
+
puts "IP_HDRINCL is supposed to be the default for IPPROTO_RAW!"
|
17
|
+
puts "setting IP_HDRINCL anyway"
|
18
|
+
ssock.setsockopt(Socket::SOL_IP, Socket::IP_HDRINCL, true)
|
19
|
+
end
|
20
|
+
rescue Errno::EPERM
|
21
|
+
$stderr.puts "Must run #{$0} as root."
|
22
|
+
exit!
|
23
|
+
end
|
24
|
+
|
25
|
+
Thread.new do
|
26
|
+
loop do
|
27
|
+
data, sender = rsock.recvfrom(8192)
|
28
|
+
port, host = Socket.unpack_sockaddr_in(sender)
|
29
|
+
out = "-"*80,
|
30
|
+
"packet received from #{host}:#{port}:",
|
31
|
+
IP.new(data).inspect_detailed,
|
32
|
+
"-"*80
|
33
|
+
puts out
|
34
|
+
$stdout.flush
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
addr = Socket.pack_sockaddr_in(1024, "localhost")
|
39
|
+
3.times do |i|
|
40
|
+
ip = IP.new do |b|
|
41
|
+
# ip_v and ip_hl are set for us by IP class
|
42
|
+
b.ip_tos = 0
|
43
|
+
b.ip_id = i+1
|
44
|
+
b.ip_off = 0
|
45
|
+
b.ip_ttl = 64
|
46
|
+
b.ip_p = Socket::IPPROTO_RAW
|
47
|
+
b.ip_src = "127.0.0.1"
|
48
|
+
b.ip_dst = "127.0.0.1"
|
49
|
+
b.body = "just another IP hacker"
|
50
|
+
b.ip_len = b.length
|
51
|
+
b.ip_sum = 0 # linux will calculate this for us (QNX won't?)
|
52
|
+
end
|
53
|
+
|
54
|
+
out = "-"*80,
|
55
|
+
"packet sent:",
|
56
|
+
ip.inspect_detailed,
|
57
|
+
"-"*80
|
58
|
+
puts out
|
59
|
+
$stdout.flush
|
60
|
+
ssock.send(ip, 0, addr)
|
61
|
+
sleep 1
|
62
|
+
end
|
data/examples/rest.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require './ip'
|
2
|
+
|
3
|
+
class Point < BitStruct
|
4
|
+
unsigned :x, 16
|
5
|
+
unsigned :y, 16
|
6
|
+
unsigned :z, 16
|
7
|
+
end
|
8
|
+
|
9
|
+
class MyPacket < IP
|
10
|
+
rest :point, Point # treat this field as Point
|
11
|
+
end
|
12
|
+
|
13
|
+
packet = MyPacket.new
|
14
|
+
point = Point.new({:x=>1, :y=>2, :z=>3})
|
15
|
+
packet.point = point
|
16
|
+
|
17
|
+
p point
|
18
|
+
p packet.point
|
19
|
+
p packet
|
20
|
+
|
21
|
+
puts packet.inspect_detailed
|
22
|
+
puts "-" * 60
|
23
|
+
|
24
|
+
require 'yaml'
|
25
|
+
|
26
|
+
packet_yaml = packet.to_yaml
|
27
|
+
puts packet_yaml
|
28
|
+
|
29
|
+
packet2 = YAML.load( packet_yaml)
|
30
|
+
p packet2
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ModuleMethodSaver
|
2
|
+
def method_missing(meth, *args, &block)
|
3
|
+
@saved ||= []
|
4
|
+
@saved << [meth, args, block]
|
5
|
+
end
|
6
|
+
|
7
|
+
def included(m)
|
8
|
+
if @saved
|
9
|
+
@saved.each do |meth, args, block|
|
10
|
+
m.send(meth, *args, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
require 'bit-struct'
|
18
|
+
|
19
|
+
module MyFields
|
20
|
+
extend ModuleMethodSaver
|
21
|
+
|
22
|
+
unsigned :x, 16
|
23
|
+
end
|
24
|
+
|
25
|
+
class LittleEndian < BitStruct
|
26
|
+
default_options :endian => :little
|
27
|
+
include MyFields
|
28
|
+
end
|
29
|
+
|
30
|
+
class BigEndian < BitStruct
|
31
|
+
default_options :endian => :big
|
32
|
+
include MyFields
|
33
|
+
end
|
34
|
+
|
35
|
+
le = LittleEndian.new
|
36
|
+
be = BigEndian.new
|
37
|
+
|
38
|
+
le.x = be.x = 256
|
39
|
+
|
40
|
+
p [le, le.to_s]
|
41
|
+
p [be, be.to_s]
|
42
|
+
|
43
|
+
|
44
|
+
__END__
|
45
|
+
|
46
|
+
Output:
|
47
|
+
|
48
|
+
[#<LittleEndian x=256>, "\000\001"]
|
49
|
+
[#<BigEndian x=256>, "\001\000"]
|
data/examples/vector.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'bit-struct'
|
2
|
+
|
3
|
+
# Example 1
|
4
|
+
class Vec < BitStruct::Vector
|
5
|
+
# these declarations apply to *each* entry in the vector:
|
6
|
+
unsigned :x, 16
|
7
|
+
signed :y, 32
|
8
|
+
|
9
|
+
Entry = struct_class # Give it a name, just for inspect to look nice
|
10
|
+
end
|
11
|
+
|
12
|
+
v = Vec.new(5) # explicit length
|
13
|
+
entry = v[3]
|
14
|
+
entry.x = 2
|
15
|
+
v[3] = entry
|
16
|
+
|
17
|
+
entry = v[4]
|
18
|
+
entry.y = -4
|
19
|
+
v[4] = entry
|
20
|
+
|
21
|
+
entry.x = 42
|
22
|
+
v << entry
|
23
|
+
|
24
|
+
p v
|
25
|
+
puts
|
26
|
+
|
27
|
+
v2 = Vec.new(v) # determines length from argument
|
28
|
+
p v2
|
29
|
+
puts
|
30
|
+
|
31
|
+
# Example 2
|
32
|
+
class Vec2 < BitStruct::Vector
|
33
|
+
class Entry < BitStruct
|
34
|
+
unsigned :x, 16
|
35
|
+
signed :y, 32
|
36
|
+
end
|
37
|
+
|
38
|
+
struct_class Entry # use Entry as the struct_class for Vec2
|
39
|
+
end
|
40
|
+
|
41
|
+
v = Vec2.new(5) # explicit length
|
42
|
+
entry = v[3]
|
43
|
+
entry.x = 2
|
44
|
+
v[3] = entry
|
45
|
+
|
46
|
+
entry = v[4]
|
47
|
+
entry.y = -4
|
48
|
+
v[4] = entry
|
49
|
+
|
50
|
+
p v
|
51
|
+
puts
|
52
|
+
|
53
|
+
puts v.inspect_detailed
|
54
|
+
puts
|
55
|
+
|
56
|
+
puts Vec2.describe
|
57
|
+
|
58
|
+
# Example 3: inheritance
|
59
|
+
class VecSub < Vec2
|
60
|
+
float :z, 32
|
61
|
+
end
|
62
|
+
|
63
|
+
vsub = VecSub.new(3)
|
64
|
+
entry = vsub[1]
|
65
|
+
entry.x = 12
|
66
|
+
entry.y = -1
|
67
|
+
entry.z = 4.5
|
68
|
+
vsub[1] = entry
|
69
|
+
p vsub
|
70
|
+
puts
|
71
|
+
|
72
|
+
# Example 4: vector field in a bitstruct
|
73
|
+
class Packet < BitStruct
|
74
|
+
unsigned :stuff, 32, "whatever"
|
75
|
+
|
76
|
+
# Using the Vec class defined above
|
77
|
+
vector :v, Vec, "a vector", :length => 5
|
78
|
+
|
79
|
+
# equivalently, using an anonymous subclass of BitStruct::Vector
|
80
|
+
vector :v2, "a vector 2", :length => 5 do
|
81
|
+
unsigned :x, 16
|
82
|
+
signed :y, 32
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
pkt = Packet.new
|
87
|
+
vec = pkt.v
|
88
|
+
entry = vec[2]
|
89
|
+
entry.x = 123
|
90
|
+
entry.y = -456
|
91
|
+
vec[2] = entry
|
92
|
+
pkt.v = vec
|
93
|
+
p pkt, pkt.v, pkt.v[2], pkt.v[2].y
|
94
|
+
puts
|
95
|
+
puts pkt.inspect_detailed
|
96
|
+
puts
|
97
|
+
|
98
|
+
puts Packet.describe(:expand => true)
|
data/lib/bit-struct.rb
ADDED
@@ -0,0 +1,549 @@
|
|
1
|
+
# Class for packed binary data, with defined bitfields and accessors for them.
|
2
|
+
# See {intro.txt}[link:../doc/files/intro_txt.html] for an overview.
|
3
|
+
#
|
4
|
+
# Data after the end of the defined fields is accessible using the +rest+
|
5
|
+
# declaration. See examples/ip.rb. Nested fields can be declared using +nest+.
|
6
|
+
# See examples/nest.rb.
|
7
|
+
#
|
8
|
+
# Note that all string methods are still available: length, grep, etc.
|
9
|
+
# The String#replace method is useful.
|
10
|
+
#
|
11
|
+
class BitStruct < String
|
12
|
+
VERSION = "0.13.1"
|
13
|
+
|
14
|
+
class Field
|
15
|
+
# Offset of field in bits.
|
16
|
+
attr_reader :offset
|
17
|
+
|
18
|
+
# Length of field in bits.
|
19
|
+
attr_reader :length
|
20
|
+
alias size length
|
21
|
+
|
22
|
+
# Name of field (used for its accessors).
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
# Options, such as :default (varies for each field subclass).
|
26
|
+
# In general, options can be provided as strings or as symbols.
|
27
|
+
attr_reader :options
|
28
|
+
|
29
|
+
# Display name of field (used for printing).
|
30
|
+
attr_reader :display_name
|
31
|
+
|
32
|
+
# Default value.
|
33
|
+
attr_reader :default
|
34
|
+
|
35
|
+
# Format for printed value of field.
|
36
|
+
attr_reader :format
|
37
|
+
|
38
|
+
# Subclasses can override this to define a default for all fields of this
|
39
|
+
# class, not just the one currently being added to a BitStruct class, a
|
40
|
+
# "default default" if you will. The global default, if #default returns
|
41
|
+
# nil, is to fill the field with zero. Most field classes just let this
|
42
|
+
# default stand. The default can be overridden per-field when a BitStruct
|
43
|
+
# class is defined.
|
44
|
+
def self.default; nil; end
|
45
|
+
|
46
|
+
# Used in describe.
|
47
|
+
def self.class_name
|
48
|
+
@class_name ||= name[/\w+$/]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Used in describe. Can be overridden per-subclass, as in NestedField.
|
52
|
+
def class_name
|
53
|
+
self.class.class_name
|
54
|
+
end
|
55
|
+
|
56
|
+
# Yield the description of this field, as an array of 5 strings: byte
|
57
|
+
# offset, type, name, size, and description. The opts hash may have:
|
58
|
+
#
|
59
|
+
# :expand :: if the value is true, expand complex fields
|
60
|
+
#
|
61
|
+
# (Subclass implementations may yield more than once for complex fields.)
|
62
|
+
#
|
63
|
+
def describe opts
|
64
|
+
bits = size
|
65
|
+
if bits > 32 and bits % 8 == 0
|
66
|
+
len_str = "%dB" % (bits/8)
|
67
|
+
else
|
68
|
+
len_str = "%db" % bits
|
69
|
+
end
|
70
|
+
|
71
|
+
byte_offset = offset / 8 + (opts[:byte_offset] || 0)
|
72
|
+
|
73
|
+
yield ["@%d" % byte_offset, class_name, name, len_str, display_name]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Options are _display_name_, _default_, and _format_ (subclasses of Field
|
77
|
+
# may add other options).
|
78
|
+
def initialize(offset, length, name, opts = {})
|
79
|
+
@offset, @length, @name, @options =
|
80
|
+
offset, length, name, opts
|
81
|
+
|
82
|
+
@display_name = opts[:display_name] || opts["display_name"]
|
83
|
+
@default = opts[:default] || opts["default"] || self.class.default
|
84
|
+
@format = opts[:format] || opts["format"]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Inspect the value of this field in the specified _obj_.
|
88
|
+
def inspect_in_object(obj, opts)
|
89
|
+
val = obj.send(name)
|
90
|
+
str =
|
91
|
+
begin
|
92
|
+
val.inspect(opts)
|
93
|
+
rescue ArgumentError # assume: "wrong number of arguments (1 for 0)"
|
94
|
+
val.inspect
|
95
|
+
end
|
96
|
+
(f=@format) ? (f % str) : str
|
97
|
+
end
|
98
|
+
|
99
|
+
# Normally, all fields show up in inspect, but some, such as padding,
|
100
|
+
# should not.
|
101
|
+
def inspectable?; true; end
|
102
|
+
end
|
103
|
+
|
104
|
+
NULL_FIELD = Field.new(0, 0, :null, :display_name => "null field")
|
105
|
+
|
106
|
+
# Raised when a field is added after an instance has been created. Fields
|
107
|
+
# cannot be added after this point.
|
108
|
+
class ClosedClassError < StandardError; end
|
109
|
+
|
110
|
+
# Raised if the chosen field name is not allowed, either because another
|
111
|
+
# field by that name exists, or because a method by that name exists.
|
112
|
+
class FieldNameError < StandardError; end
|
113
|
+
|
114
|
+
@default_options = {}
|
115
|
+
|
116
|
+
@initial_value = nil
|
117
|
+
@closed = nil
|
118
|
+
@rest_field = nil
|
119
|
+
@note = nil
|
120
|
+
|
121
|
+
class << self
|
122
|
+
def inherited cl
|
123
|
+
cl.instance_eval do
|
124
|
+
@initial_value = nil
|
125
|
+
@closed = nil
|
126
|
+
@rest_field = nil
|
127
|
+
@note = nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# ------------------------
|
132
|
+
# :section: field access methods
|
133
|
+
#
|
134
|
+
# For introspection and metaprogramming.
|
135
|
+
#
|
136
|
+
# ------------------------
|
137
|
+
|
138
|
+
# Return the list of fields for this class.
|
139
|
+
def fields
|
140
|
+
@fields ||= self == BitStruct ? [] : superclass.fields.dup
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return the list of fields defined by this class, not inherited
|
144
|
+
# from the superclass.
|
145
|
+
def own_fields
|
146
|
+
@own_fields ||= []
|
147
|
+
end
|
148
|
+
|
149
|
+
# Add a field to the BitStruct (usually, this is only used internally).
|
150
|
+
def add_field(name, length, opts = {})
|
151
|
+
round_byte_length ## just to make sure this has been calculated
|
152
|
+
## before adding anything
|
153
|
+
|
154
|
+
name = name.to_sym
|
155
|
+
|
156
|
+
if @closed
|
157
|
+
raise ClosedClassError, "Cannot add field #{name}: " +
|
158
|
+
"The definition of the #{self.inspect} BitStruct class is closed."
|
159
|
+
end
|
160
|
+
|
161
|
+
if fields.find {|f|f.name == name}
|
162
|
+
raise FieldNameError, "Field #{name} is already defined as a field."
|
163
|
+
end
|
164
|
+
|
165
|
+
if instance_methods(true).find {|m| m == name}
|
166
|
+
if opts[:allow_method_conflict] || opts["allow_method_conflict"]
|
167
|
+
warn "Field #{name} is already defined as a method."
|
168
|
+
else
|
169
|
+
raise FieldNameError,"Field #{name} is already defined as a method."
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
field_class = opts[:field_class]
|
174
|
+
|
175
|
+
prev = fields[-1] || NULL_FIELD
|
176
|
+
offset = prev.offset + prev.length
|
177
|
+
field = field_class.new(offset, length, name, opts)
|
178
|
+
field.add_accessors_to(self)
|
179
|
+
fields << field
|
180
|
+
own_fields << field
|
181
|
+
@bit_length += field.length
|
182
|
+
@round_byte_length = (bit_length/8.0).ceil
|
183
|
+
|
184
|
+
if @initial_value
|
185
|
+
diff = @round_byte_length - @initial_value.length
|
186
|
+
if diff > 0
|
187
|
+
@initial_value << "\0" * diff
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
field
|
192
|
+
end
|
193
|
+
|
194
|
+
def parse_options(ary, default_name, default_field_class) # :nodoc:
|
195
|
+
opts = ary.grep(Hash).first || {}
|
196
|
+
opts = default_options.merge(opts)
|
197
|
+
|
198
|
+
opts[:display_name] = ary.grep(String).first || default_name
|
199
|
+
opts[:field_class] = ary.grep(Class).first || default_field_class
|
200
|
+
|
201
|
+
opts
|
202
|
+
end
|
203
|
+
|
204
|
+
# Get or set the hash of default options for the class, which apply to all
|
205
|
+
# fields. Changes take effect immediately, so can be used alternatingly with
|
206
|
+
# blocks of field declarations. If +h+ is provided, update the default
|
207
|
+
# options with that hash. Default options are inherited.
|
208
|
+
#
|
209
|
+
# This is especially useful with the <tt>:endian => val</tt> option.
|
210
|
+
def default_options h = nil
|
211
|
+
@default_options ||= superclass.default_options.dup
|
212
|
+
if h
|
213
|
+
@default_options.merge! h
|
214
|
+
end
|
215
|
+
@default_options
|
216
|
+
end
|
217
|
+
|
218
|
+
# Length, in bits, of this object.
|
219
|
+
def bit_length
|
220
|
+
@bit_length ||= fields.inject(0) {|a, f| a + f.length}
|
221
|
+
end
|
222
|
+
|
223
|
+
# Length, in bytes (rounded up), of this object.
|
224
|
+
def round_byte_length
|
225
|
+
@round_byte_length ||= (bit_length/8.0).ceil
|
226
|
+
end
|
227
|
+
|
228
|
+
def closed! # :nodoc:
|
229
|
+
@closed = true
|
230
|
+
end
|
231
|
+
|
232
|
+
def field_by_name name
|
233
|
+
@field_by_name ||= {}
|
234
|
+
field = @field_by_name[name]
|
235
|
+
unless field
|
236
|
+
field = fields.find {|f| f.name == name}
|
237
|
+
@field_by_name[name] = field if field
|
238
|
+
end
|
239
|
+
field
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Return the list of fields for this class.
|
244
|
+
def fields
|
245
|
+
self.class.fields
|
246
|
+
end
|
247
|
+
|
248
|
+
# Return the field with the given name.
|
249
|
+
def field_by_name name
|
250
|
+
self.class.field_by_name name
|
251
|
+
end
|
252
|
+
|
253
|
+
# ------------------------
|
254
|
+
# :section: metadata inspection methods
|
255
|
+
#
|
256
|
+
# Methods to textually describe the format of a BitStruct subclass.
|
257
|
+
#
|
258
|
+
# ------------------------
|
259
|
+
|
260
|
+
class << self
|
261
|
+
# Default format for describe. Fields are byte, type, name, size,
|
262
|
+
# and description.
|
263
|
+
DESCRIBE_FORMAT = "%8s: %-12s %-14s[%4s] %s"
|
264
|
+
|
265
|
+
# Can be overridden to use a different format.
|
266
|
+
def describe_format
|
267
|
+
DESCRIBE_FORMAT
|
268
|
+
end
|
269
|
+
|
270
|
+
# Textually describe the fields of this class of BitStructs.
|
271
|
+
# Returns a printable table (array of line strings), based on +fmt+,
|
272
|
+
# which defaults to #describe_format, which defaults to +DESCRIBE_FORMAT+.
|
273
|
+
def describe(fmt = nil, opts = {})
|
274
|
+
if fmt.kind_of? Hash
|
275
|
+
opts = fmt; fmt = nil
|
276
|
+
end
|
277
|
+
|
278
|
+
if block_given?
|
279
|
+
fields.each do |field|
|
280
|
+
field.describe(opts) do |desc|
|
281
|
+
yield desc
|
282
|
+
end
|
283
|
+
end
|
284
|
+
nil
|
285
|
+
|
286
|
+
else
|
287
|
+
fmt ||= describe_format
|
288
|
+
|
289
|
+
result = []
|
290
|
+
|
291
|
+
unless opts[:omit_header]
|
292
|
+
result << fmt % ["byte", "type", "name", "size", "description"]
|
293
|
+
result << "-"*70
|
294
|
+
end
|
295
|
+
|
296
|
+
fields.each do |field|
|
297
|
+
field.describe(opts) do |desc|
|
298
|
+
result << fmt % desc
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
unless opts[:omit_footer]
|
303
|
+
result << @note if @note
|
304
|
+
end
|
305
|
+
|
306
|
+
result
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Subclasses can use this to append a string (or several) to the #describe
|
311
|
+
# output. Notes are not cumulative with inheritance. When used with no
|
312
|
+
# arguments simply returns the note string
|
313
|
+
def note(*str)
|
314
|
+
@note = str unless str.empty?
|
315
|
+
@note
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# ------------------------
|
320
|
+
# :section: initialization and conversion methods
|
321
|
+
#
|
322
|
+
# ------------------------
|
323
|
+
|
324
|
+
# Initialize the string with the given string or bitstruct, or with a hash of
|
325
|
+
# field=>value pairs, or with the defaults for the BitStruct subclass, or
|
326
|
+
# with an IO or other object with a #read method. Fields can be strings or
|
327
|
+
# symbols. Finally, if a block is given, yield the instance for modification
|
328
|
+
# using accessors.
|
329
|
+
def initialize(value = nil) # :yields: instance
|
330
|
+
self << self.class.initial_value
|
331
|
+
|
332
|
+
case value
|
333
|
+
when Hash
|
334
|
+
value.each do |k, v|
|
335
|
+
send "#{k}=", v
|
336
|
+
end
|
337
|
+
|
338
|
+
when nil
|
339
|
+
|
340
|
+
else
|
341
|
+
if value.respond_to?(:read)
|
342
|
+
value = value.read(self.class.round_byte_length)
|
343
|
+
end
|
344
|
+
|
345
|
+
self[0, value.length] = value
|
346
|
+
end
|
347
|
+
|
348
|
+
self.class.closed!
|
349
|
+
yield self if block_given?
|
350
|
+
end
|
351
|
+
|
352
|
+
DEFAULT_TO_H_OPTS = {
|
353
|
+
:convert_keys => :to_sym,
|
354
|
+
:include_rest => true
|
355
|
+
}
|
356
|
+
|
357
|
+
# Returns a hash of {name=>value,...} for each field. By default, include
|
358
|
+
# the rest field.
|
359
|
+
# Keys are symbols derived from field names using +to_sym+, unless
|
360
|
+
# <tt>opts[:convert_keys]<\tt> is set to some other method name.
|
361
|
+
def to_h(opts = DEFAULT_TO_H_OPTS)
|
362
|
+
converter = opts[:convert_keys] || :to_sym
|
363
|
+
|
364
|
+
fields_for_to_h = fields
|
365
|
+
if opts[:include_rest] and (rest_field = self.class.rest_field)
|
366
|
+
fields_for_to_h += [rest_field]
|
367
|
+
end
|
368
|
+
|
369
|
+
fields_for_to_h.inject({}) do |h,f|
|
370
|
+
h[f.name.send(converter)] = send(f.name)
|
371
|
+
h
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Returns an array of values of the fields of the BitStruct. By default,
|
376
|
+
# include the rest field.
|
377
|
+
def to_a(include_rest = true)
|
378
|
+
ary =
|
379
|
+
fields.map do |f|
|
380
|
+
send(f.name)
|
381
|
+
end
|
382
|
+
|
383
|
+
if include_rest and (rest_field = self.class.rest_field)
|
384
|
+
ary << send(rest_field.name)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
class << self
|
389
|
+
# The unique "prototype" object from which new instances are copied.
|
390
|
+
# The fields of this instance can be modified in the class definition
|
391
|
+
# to set default values for the fields in that class. (Otherwise, defaults
|
392
|
+
# defined by the fields themselves are used.) A copy of this object is
|
393
|
+
# inherited in subclasses, which they may override using defaults and
|
394
|
+
# by writing to the initial_value object itself.
|
395
|
+
#
|
396
|
+
# If called with a block, yield the initial value object before returning
|
397
|
+
# it. Useful for customization within a class definition.
|
398
|
+
#
|
399
|
+
def initial_value # :yields: the initial value
|
400
|
+
unless @initial_value
|
401
|
+
iv = defined?(superclass.initial_value) ?
|
402
|
+
superclass.initial_value.dup : ""
|
403
|
+
if iv.length < round_byte_length
|
404
|
+
iv << "\0" * (round_byte_length - iv.length)
|
405
|
+
end
|
406
|
+
|
407
|
+
@initial_value = "" # Serves as initval while the real initval is inited
|
408
|
+
@initial_value = new(iv)
|
409
|
+
@closed = false # only creating the first _real_ instance closes.
|
410
|
+
|
411
|
+
fields.each do |field|
|
412
|
+
@initial_value.send("#{field.name}=", field.default) if field.default
|
413
|
+
end
|
414
|
+
end
|
415
|
+
yield @initial_value if block_given?
|
416
|
+
@initial_value
|
417
|
+
end
|
418
|
+
|
419
|
+
# Take +data+ (a string or BitStruct) and parse it into instances of
|
420
|
+
# the +classes+, returning them in an array. The classes can be given
|
421
|
+
# as an array or a separate arguments. (For parsing a string into a _single_
|
422
|
+
# BitStruct instance, just use the #new method with the string as an arg.)
|
423
|
+
def parse(data, *classes)
|
424
|
+
classes.flatten.map do |c|
|
425
|
+
c.new(data.slice!(0...c.round_byte_length))
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Join the given structs (array or multiple args) as a string.
|
430
|
+
# Actually, the inherited String#+ instance method is the same, as is using
|
431
|
+
# Array#join.
|
432
|
+
def join(*structs)
|
433
|
+
structs.flatten.map {|struct| struct.to_s}.join("")
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# ------------------------
|
438
|
+
# :section: inspection methods
|
439
|
+
#
|
440
|
+
# ------------------------
|
441
|
+
|
442
|
+
DEFAULT_INSPECT_OPTS = {
|
443
|
+
:format => "#<%s %s>",
|
444
|
+
:field_format => "%s=%s",
|
445
|
+
:separator => ", ",
|
446
|
+
:field_name_meth => :name,
|
447
|
+
:include_rest => true,
|
448
|
+
:brackets => ["[", "]"],
|
449
|
+
:include_class => true,
|
450
|
+
:simple_format => "<%s>"
|
451
|
+
}
|
452
|
+
|
453
|
+
DETAILED_INSPECT_OPTS = {
|
454
|
+
:format => "%s:\n%s",
|
455
|
+
:field_format => "%30s = %s",
|
456
|
+
:separator => "\n",
|
457
|
+
:field_name_meth => :display_name,
|
458
|
+
:include_rest => true,
|
459
|
+
:brackets => [nil, "\n"],
|
460
|
+
:include_class => true,
|
461
|
+
:simple_format => "\n%s"
|
462
|
+
}
|
463
|
+
|
464
|
+
# A standard inspect method which does not add newlines.
|
465
|
+
def inspect(opts = DEFAULT_INSPECT_OPTS)
|
466
|
+
field_format = opts[:field_format]
|
467
|
+
field_name_meth = opts[:field_name_meth]
|
468
|
+
|
469
|
+
fields_for_inspect = fields.select {|field| field.inspectable?}
|
470
|
+
if opts[:include_rest] and (rest_field = self.class.rest_field)
|
471
|
+
fields_for_inspect << rest_field
|
472
|
+
end
|
473
|
+
|
474
|
+
ary = fields_for_inspect.map do |field|
|
475
|
+
field_format %
|
476
|
+
[field.send(field_name_meth),
|
477
|
+
field.inspect_in_object(self, opts)]
|
478
|
+
end
|
479
|
+
|
480
|
+
body = ary.join(opts[:separator])
|
481
|
+
|
482
|
+
if opts[:include_class]
|
483
|
+
opts[:format] % [self.class, body]
|
484
|
+
else
|
485
|
+
opts[:simple_format] % body
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
# A more visually appealing inspect method that puts each field/value on
|
490
|
+
# a separate line. Very useful when output is scrolling by on a screen.
|
491
|
+
#
|
492
|
+
# (This is actually a convenience method to call #inspect with the
|
493
|
+
# DETAILED_INSPECT_OPTS opts.)
|
494
|
+
def inspect_detailed
|
495
|
+
inspect(DETAILED_INSPECT_OPTS)
|
496
|
+
end
|
497
|
+
|
498
|
+
# ------------------------
|
499
|
+
# :section: field declaration methods
|
500
|
+
#
|
501
|
+
# ------------------------
|
502
|
+
|
503
|
+
# Define accessors for a variable length substring from the end of
|
504
|
+
# the defined fields to the end of the BitStruct. The _rest_ may behave as
|
505
|
+
# a String or as some other String or BitStruct subclass.
|
506
|
+
#
|
507
|
+
# This does not add a field, which is useful because a superclass can have
|
508
|
+
# a rest method which accesses subclass data. In particular, #rest does
|
509
|
+
# not affect the #round_byte_length class method. Of course, any data
|
510
|
+
# in rest does add to the #length of the BitStruct, calculated as a string.
|
511
|
+
# Also, _rest_ is not inherited.
|
512
|
+
#
|
513
|
+
# The +ary+ argument(s) work as follows:
|
514
|
+
#
|
515
|
+
# If a class is provided, use it for the Field class (String by default).
|
516
|
+
# If a string is provided, use it for the display_name (+name+ by default).
|
517
|
+
# If a hash is provided, use it for options.
|
518
|
+
#
|
519
|
+
# *Warning*: the rest reader method returns a copy of the field, so
|
520
|
+
# accessors on that returned value do not affect the original rest field.
|
521
|
+
#
|
522
|
+
def self.rest(name, *ary)
|
523
|
+
if @rest_field
|
524
|
+
raise ArgumentError, "Duplicate rest field: #{name.inspect}."
|
525
|
+
end
|
526
|
+
|
527
|
+
opts = parse_options(ary, name, String)
|
528
|
+
offset = round_byte_length
|
529
|
+
byte_range = offset..-1
|
530
|
+
class_eval do
|
531
|
+
field_class = opts[:field_class]
|
532
|
+
define_method name do ||
|
533
|
+
field_class.new(self[byte_range])
|
534
|
+
end
|
535
|
+
|
536
|
+
define_method "#{name}=" do |val|
|
537
|
+
self[byte_range] = val
|
538
|
+
end
|
539
|
+
|
540
|
+
@rest_field = Field.new(offset, -1, name, {
|
541
|
+
:display_name => opts[:display_name],
|
542
|
+
:rest_class => field_class
|
543
|
+
})
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# Not included with the other fields, but accessible separately.
|
548
|
+
def self.rest_field; @rest_field; end
|
549
|
+
end
|