181 lines
4.9 KiB
Ruby
181 lines
4.9 KiB
Ruby
|
require 'bit-struct/vector'
|
||
|
|
||
|
class BitStruct
|
||
|
# Class for embedding a BitStruct::Vector as a field within a BitStruct.
|
||
|
# Declared with BitStruct.vector.
|
||
|
class VectorField < Field
|
||
|
def initialize(*args)
|
||
|
super
|
||
|
end
|
||
|
|
||
|
# Used in describe.
|
||
|
def self.class_name
|
||
|
@class_name ||= "vector"
|
||
|
end
|
||
|
|
||
|
def class_name
|
||
|
@class_name ||= vector_class.name[/\w+$/]
|
||
|
end
|
||
|
|
||
|
def vector_class
|
||
|
@vector_class ||= options[:vector_class] || options["vector_class"]
|
||
|
end
|
||
|
|
||
|
def describe opts
|
||
|
if opts[:expand]
|
||
|
opts = opts.dup
|
||
|
opts[:byte_offset] = offset / 8
|
||
|
opts[:omit_header] = opts[:omit_footer] = true
|
||
|
vector_class.describe(nil, opts) {|desc| yield desc}
|
||
|
else
|
||
|
super
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def add_accessors_to(cl, attr = name) # :nodoc:
|
||
|
unless offset % 8 == 0
|
||
|
raise ArgumentError,
|
||
|
"Bad offset, #{offset}, for vector field #{name}." +
|
||
|
" Must be multiple of 8."
|
||
|
end
|
||
|
|
||
|
unless length % 8 == 0
|
||
|
raise ArgumentError,
|
||
|
"Bad length, #{length}, for vector field #{name}." +
|
||
|
" Must be multiple of 8."
|
||
|
end
|
||
|
|
||
|
offset_byte = offset / 8
|
||
|
length_byte = length / 8
|
||
|
last_byte = offset_byte + length_byte - 1
|
||
|
byte_range = offset_byte..last_byte
|
||
|
|
||
|
vc = vector_class
|
||
|
|
||
|
cl.class_eval do
|
||
|
define_method attr do ||
|
||
|
vc.new(self[byte_range])
|
||
|
end
|
||
|
|
||
|
define_method "#{attr}=" do |val|
|
||
|
if val.length != length_byte
|
||
|
raise ArgumentError, "Size mismatch in vector field assignment " +
|
||
|
"to #{attr} with value #{val.inspect}"
|
||
|
end
|
||
|
|
||
|
if val.class != vc
|
||
|
warn "Type mismatch in vector field assignment " +
|
||
|
"to #{attr} with value #{val.inspect}"
|
||
|
end
|
||
|
|
||
|
self[byte_range] = val
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class << self
|
||
|
# Define a vector field in the current subclass of BitStruct,
|
||
|
# with the given _name_.
|
||
|
#
|
||
|
# In _rest_:
|
||
|
#
|
||
|
# If a class is provided, use it for the Vector class, otherwise
|
||
|
# the block must define the entry fields. The two forms looks like
|
||
|
# this:
|
||
|
#
|
||
|
# class Vec < BitStruct::Vector
|
||
|
# # these declarations apply to *each* entry in the vector:
|
||
|
# unsigned :x, 16
|
||
|
# signed :y, 32
|
||
|
# end
|
||
|
#
|
||
|
# class Packet < BitStruct
|
||
|
# # Using the Vec class defined above
|
||
|
# vector :v, Vec, "a vector", :length => 5
|
||
|
#
|
||
|
# # equivalently, using an anonymous subclass of BitStruct::Vector
|
||
|
# vector :v2, "a vector", :length => 5 do
|
||
|
# unsigned :x, 16
|
||
|
# signed :y, 32
|
||
|
# end
|
||
|
# end
|
||
|
#
|
||
|
# If a string is provided, use it for the display_name.
|
||
|
# If a hash is provided, use it for options.
|
||
|
#
|
||
|
# WARNING: the accessors have COPY semantics, not reference. When you call a
|
||
|
# reader method to get the vector structure, you get a *copy* of that data.
|
||
|
#
|
||
|
# For example, to modify the numeric fields in a Packet as defined above:
|
||
|
#
|
||
|
# pkt = Packet.new
|
||
|
# vec = pkt.v
|
||
|
# entry = vec[2]
|
||
|
# entry.x = 123
|
||
|
# entry.y = -456
|
||
|
# vec[2] = entry
|
||
|
# pkt.v = vec
|
||
|
#
|
||
|
def vector(name, *rest, &block)
|
||
|
opts = parse_options(rest, name, nil)
|
||
|
cl = opts[:field_class]
|
||
|
opts[:field_class] = VectorField
|
||
|
|
||
|
unless (block and not cl) or (cl and not block)
|
||
|
raise ArgumentError,
|
||
|
"vector must have either a class or a block, but not both"
|
||
|
end
|
||
|
|
||
|
case
|
||
|
when cl == nil
|
||
|
vector_class = Class.new(BitStruct::Vector)
|
||
|
vector_class.class_eval(&block)
|
||
|
|
||
|
when cl < BitStruct
|
||
|
vector_class = Class.new(BitStruct::Vector)
|
||
|
vector_class.struct_class cl
|
||
|
|
||
|
when cl < BitStruct::Vector
|
||
|
vector_class = cl
|
||
|
|
||
|
else raise ArgumentError, "Bad vector class: #{cl.inspect}"
|
||
|
end
|
||
|
|
||
|
vector_class.default_options default_options
|
||
|
|
||
|
length = opts[:length] ## what about :length => :lenfield
|
||
|
unless length
|
||
|
raise ArgumentError, "Must provide length as :length => N"
|
||
|
end
|
||
|
|
||
|
opts[:default] ||= vector_class.new(length) ## nil if variable length
|
||
|
opts[:vector_class] = vector_class
|
||
|
|
||
|
bit_length = vector_class.struct_class.round_byte_length * 8 * length
|
||
|
|
||
|
field = add_field(name, bit_length, opts)
|
||
|
field
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
__END__
|
||
|
|
||
|
# The above does not permit a Vector to be embedded in another
|
||
|
# BitStruct. Hypothetical syntax for doing so:
|
||
|
|
||
|
class Packet < BitStruct
|
||
|
unsigned :stuff, 24, "whatever"
|
||
|
|
||
|
# Using the Vec class defined above
|
||
|
vector :v, Vec, "a vector", :length => 5
|
||
|
|
||
|
# equivalently, using an anonymous subclass of BitStruct::Vector
|
||
|
vector :v, "a vector", :length => 5 do
|
||
|
unsigned :x, 16
|
||
|
signed :y, 32
|
||
|
end
|
||
|
end
|
||
|
|