metasploit-framework/lib/bit-struct/signed-field.rb

284 lines
8.8 KiB
Ruby

require 'bit-struct/bit-struct'
class BitStruct
# Class for signed integers in network order, 1-16 bits, or 8n bits.
# Declared with BitStruct.signed.
class SignedField < Field
# Used in describe.
def self.class_name
@class_name ||= "signed"
end
def add_accessors_to(cl, attr = name) # :nodoc:
offset_byte = offset / 8
offset_bit = offset % 8
length_bit = offset_bit + length
length_byte = (length_bit/8.0).ceil
last_byte = offset_byte + length_byte - 1
max = 2**length-1
mid = 2**(length-1)
max_unsigned = 2**length
to_signed = proc {|n| (n>=mid) ? n - max_unsigned : n}
# to_signed = proc {|n| (n>=mid) ? -((n ^ max) + 1) : n}
divisor = options[:fixed] || options["fixed"]
divisor_f = divisor && divisor.to_f
# if divisor and not divisor.is_a? Fixnum
# raise ArgumentError, "fixed-point divisor must be a fixnum"
# end
endian = (options[:endian] || options["endian"]).to_s
case endian
when "native"
ctl = length_byte <= 2 ? "s" : "l"
if length == 16 or length == 32
to_signed = proc {|n| n}
# with pack support, to_signed can be replaced with no-op
end
when "little"
ctl = length_byte <= 2 ? "v" : "V"
when "network", "big", ""
ctl = length_byte <= 2 ? "n" : "N"
else
raise ArgumentError,
"Unrecognized endian option: #{endian.inspect}"
end
data_is_big_endian =
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
if length_byte == 1
rest = 8 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0]
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0]
cl.class_eval do
if divisor
define_method attr do ||
to_signed[(self[offset_byte] & mask) >> rest] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
else
define_method attr do ||
to_signed[(self[offset_byte] & mask) >> rest]
end
define_method "#{attr}=" do |val|
self[offset_byte] =
(self[offset_byte] & mask2) | ((val<<rest) & mask)
end
end
end
elsif offset_bit == 0 and length % 8 == 0
field_length = length
byte_range = offset_byte..last_byte
cl.class_eval do
case field_length
when 8
if divisor
define_method attr do ||
to_signed[self[offset_byte]] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[offset_byte] = val
end
else
define_method attr do ||
to_signed[self[offset_byte]]
end
define_method "#{attr}=" do |val|
self[offset_byte] = val
end
end
when 16, 32
if divisor
define_method attr do ||
to_signed[self[byte_range].unpack(ctl).first] / divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
self[byte_range] = [val].pack(ctl)
end
else
define_method attr do ||
to_signed[self[byte_range].unpack(ctl).first]
end
define_method "#{attr}=" do |val|
self[byte_range] = [val].pack(ctl)
end
end
else
reader_helper = proc do |substr|
bytes = substr.unpack("C*")
bytes.reverse! unless data_is_big_endian
bytes.inject do |sum, byte|
(sum << 8) + byte
end
end
writer_helper = proc do |val|
bytes = []
val += max_unsigned if val < 0
while val > 0
bytes.push val % 256
val = val >> 8
end
if bytes.length < length_byte
bytes.concat [0] * (length_byte - bytes.length)
end
bytes.reverse! if data_is_big_endian
bytes.pack("C*")
end
if divisor
define_method attr do ||
to_signed[reader_helper[self[byte_range]] / divisor_f]
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[(val * divisor).round]
end
else
define_method attr do ||
to_signed[reader_helper[self[byte_range]]]
end
define_method "#{attr}=" do |val|
self[byte_range] = writer_helper[val]
end
end
end
end
elsif length_byte == 2 # unaligned field that fits within two whole bytes
byte_range = offset_byte..last_byte
rest = 16 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B16").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B16").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest] /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
else
define_method attr do ||
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest]
end
define_method "#{attr}=" do |val|
x = (self[byte_range].unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)
end
end
end
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
byte_range = offset_byte..last_byte
rest = 32 - length_bit
mask = ["0"*offset_bit + "1"*length + "0"*rest]
mask = mask.pack("B32").unpack(ctl).first
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
mask2 = mask2.pack("B32").unpack(ctl).first
cl.class_eval do
if divisor
define_method attr do ||
bytes = self[byte_range]
bytes << 0
to_signed[((bytes.unpack(ctl).first & mask) >> rest)] /
divisor_f
end
define_method "#{attr}=" do |val|
val = (val * divisor).round
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
else
define_method attr do ||
bytes = self[byte_range]
bytes << 0
to_signed[(bytes.unpack(ctl).first & mask) >> rest]
end
define_method "#{attr}=" do |val|
bytes = self[byte_range]
bytes << 0
x = (bytes.unpack(ctl).first & mask2) |
((val<<rest) & mask)
self[byte_range] = [x].pack(ctl)[0..2]
end
end
end
else
raise "unsupported: #{inspect}"
end
end
end
class << self
# Define a signed integer field in the current subclass of BitStruct,
# with the given _name_ and _length_ (in bits).
#
# If a class is provided, use it for the Field class.
# If a string is provided, use it for the display_name.
# If a hash is provided, use it for options.
#
# SignedField adds the <tt>:fixed => divisor</tt> option, which specifies
# that the internally stored value is interpreted as a fixed point real
# number with the specified +divisor+.
#
# The <tt>:endian => :native</tt> option overrides the default of
# <tt>:network</tt> byte ordering, in favor of native byte ordering. Also
# permitted are <tt>:big</tt> (same as <tt>:network</tt>) and
# <tt>:little</tt>.
#
def signed name, length, *rest
opts = parse_options(rest, name, SignedField)
add_field(name, length, opts)
end
end
end